重庆丰标建设网站,石家庄做网络科技公司,重视网站阵地建设,济宁贵网站建设一.麻烦讲述一下Hashmap的扩容原理
jdk1.8中的hashmap扩容原理
1.put流程图
首先贴一张图(图片来源于传送门#xff09;#xff0c;多谢大佬的美图#xff0c;此图已经完美的描述了put的整个流程#xff0c;我也就不想自己画了#xff0c;嘿嘿:
2.hashmap中几个比较重…一.麻烦讲述一下Hashmap的扩容原理
jdk1.8中的hashmap扩容原理
1.put流程图
首先贴一张图(图片来源于传送门多谢大佬的美图此图已经完美的描述了put的整个流程我也就不想自己画了嘿嘿:
2.hashmap中几个比较重要的成员变量
HashMap在底层数据结构上采用了数组链表红黑树在其源码中有几个比较重要的成员变量需要记住,这几个成员变量也是在扩容中肯定会遇到的:
// 默认的初始化容量16,必须为2的幂
static final int DEFAULT_INITIAL_CAPACITY 1 4; // aka 16
//默认的负载因子0.75f
static final float DEFAULT_LOAD_FACTOR 0.75f;
//当某个桶的阈值大于8会将链表的结构转换为红黑树
static final int TREEIFY_THRESHOLD 8;
//当某个桶的阈值小于6时会将原本的树结构转换为链表结构
static final int UNTREEIFY_THRESHOLD 6;
//当Node数组的长度大于64才允许将链表转换为红黑树否则应该直接扩容而不是将链表进行树化
//此处的含义就是如果一个链表的长度超过了8但是Node数组的长度小于64的话一般以扩容来处理
而不是将链表树化
static final int MIN_TREEIFY_CAPACITY 64;
//阈值capacity * load factor
int threshold;3.扩容的详细步骤
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {NodeK,V[] tab; NodeK,V p; int n, i;if ((tab table) null || (n tab.length) 0)n (tab resize()).length;if ((p tab[i (n - 1) hash]) null)tab[i] newNode(hash, key, value, null);else {NodeK,V e; K k;if (p.hash hash ((k p.key) key || (key ! null key.equals(k))))e p;else if (p instanceof TreeNode)e ((TreeNodeK,V)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount 0; ; binCount) {if ((e p.next) null) {p.next newNode(hash, key, value, null);if (binCount TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash hash ((k e.key) key || (key ! null key.equals(k))))break;p e;}}if (e ! null) { // existing mapping for keyV oldValue e.value;if (!onlyIfAbsent || oldValue null)e.value value;afterNodeAccess(e);return oldValue;}}modCount;if (size threshold)resize();afterNodeInsertion(evict);return null;}首先从put和putVal中我们发现扩容的核心源码是resize(): 在分析resize()方法之前我们首先了解一下在那些情况下hashmap会产生扩容动作 在hashmap中有三种情况会触发扩容,分别是: 第一种使用默认构造方法初始化HashMap。从前文可以知道HashMap在一开始初始化的时候会返回一个空的table并且thershold为0。因此第一次扩容的容量为默认值DEFAULT_INITIAL_CAPACITY也就是16。同时threshold DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR 12。 第二种指定初始容量的构造方法初始化HashMap。那么从下面源码可以看到初始容量会等于threshold接着threshold 当前的容量threshold * DEFAULT_LOAD_FACTOR。
第三种HashMap不是第一次扩容。如果HashMap已经扩容过的话那么每次table的容量以及threshold量为原有的两倍。
这边也可以引申到一个问题HashMap是先插入还是先扩容HashMap是先插入数据再进行扩容的但是如果是刚刚初始化容器的时候是先扩容再插入数据(这一点可以从putValue方法源码中看出)。 接下来是重点resize()方法
final NodeK,V[] resize() {NodeK,V[] oldTab table;int oldCap (oldTab null) ? 0 : oldTab.length;int oldThr threshold;int newCap, newThr 0;if (oldCap 0) {if (oldCap MAXIMUM_CAPACITY) {threshold Integer.MAX_VALUE;return oldTab;}else if ((newCap oldCap 1) MAXIMUM_CAPACITY oldCap DEFAULT_INITIAL_CAPACITY)newThr oldThr 1; // double threshold}else if (oldThr 0) // initial capacity was placed in thresholdnewCap oldThr;else { // zero initial threshold signifies using defaultsnewCap DEFAULT_INITIAL_CAPACITY;newThr (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr 0) {float ft (float)newCap * loadFactor;newThr (newCap MAXIMUM_CAPACITY ft (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold newThr;SuppressWarnings({rawtypes,unchecked})NodeK,V[] newTab (NodeK,V[])new Node[newCap];table newTab;if (oldTab ! null) {for (int j 0; j oldCap; j) {NodeK,V e;if ((e oldTab[j]) ! null) {oldTab[j] null;if (e.next null)newTab[e.hash (newCap - 1)] e;else if (e instanceof TreeNode)((TreeNodeK,V)e).split(this, newTab, j, oldCap);else { // preserve orderNodeK,V loHead null, loTail null;NodeK,V hiHead null, hiTail null;NodeK,V next;do {next e.next;if ((e.hash oldCap) 0) {if (loTail null)loHead e;elseloTail.next e;loTail e;}else {if (hiTail null)hiHead e;elsehiTail.next e;hiTail e;}} while ((e next) ! null);if (loTail ! null) {loTail.next null;newTab[j] loHead;}if (hiTail ! null) {hiTail.next null;newTab[j oldCap] hiHead;}}}}}return newTab;}从resize()方法中可以看到一点:最重要的一个判断是(e.hash oldCap),从此处会将链表分成两段低位链表和高位链表低位链表的节点位置不动高位链表节点的位置是原来的位置加上老的链表的位置。 原理如下: 原table中的数据索引到新的table中要么保持位置不变要么位置 oldCap原表大小 原表索引 先放一下求余公式X % 2^n X (2^n - 1) x对2的n次方求余 length 2的n次方 原表索引 hash的后N位 length - 1 新表索引 hash的后N1位 length 1 - 1 如果还有不理解的可以看这位大神的博客:传送门
jdk1.7中为什么多线程情况下会出现死循环?
答案:直接一个传送门