前言Learn && Live
虚度年华浮萍于世,勤学善思至死不渝
Hey,欢迎阅读Connor学Java系列,这个系列记录了我的Java基础知识学习、复盘过程,欢迎各位大佬阅读斧正!原创不易,转载请注明出处:http://t.csdn.cn/4Kxtm,话不多说我们马上开始!
HashMap扩容机制源码分析有两种情况会调用resize方法
(1)第一次调用put方法时,会调用resize方法对table数组进行初始化。默认大小16
(2)扩容时调用resize,即size > threshold时,table数组大小翻倍
final Node[] resize() { Node [] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; // 1 if (oldCap > 0) { // 1-(1) if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 1-(2) else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } // 2 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; // 3 else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 2-(1) 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"}) // 1 Node [] newTab = (Node [])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node e; if ((e = oldTab[j]) != null) { oldTab[j] = null; // 2 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; // 3 else if (e instanceof TreeNode) ((TreeNode )e).split(this, newTab, j, oldCap); // 4 else { // preserve order Node loHead = null, loTail = null; Node hiHead = null, hiTail = null; Node next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.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; }
先明确源码中的几个变量,方便之后分析
-
Node
[] oldTab = table:旧数组 -
int oldCap = (oldTab == null) ? 0 : oldTab.length:旧数组的容量
-
int oldThr = threshold:旧数组的阈值
-
int newCap, newThr = 0:新数组的容量和阈值
数组容量与阈值的变化
1.oldCap > 0,此时说明在这之前已经调用过resize至少一次
(1)oldCap >= MAXIMUM_CAPACITY,容量达到最大值,更新阈值为最大值,返回当前数组,不再扩容
(2)否则,newCap = oldCap << 1、newThr = oldThr << 1,容量和阈值均扩大为原来的2倍
2.oldCap <= 0,且oldThr(threshold) > 0,对应初始化map时调用resize的情况。
(1)此时oldThr的值等于 threshold,是通过 tableSizeFor 方法得到的一个2的n次幂的值。因此,需要把 oldThr 的值,也就是 threshold ,赋值给新数组的容量 newCap,以保证数组的容量是2的n次幂。
(2)计算newThr为容量 * 加载因子,并将它赋值给threshold,用来表示下次需要扩容的阈值
3.oldCap和oldThr均为负数,说明是由无参构造创建的,容量和阈值直接采用默认值
数组内容的变化
1.根据扩大的容量和阈值创建新数组
2.遍历旧数组,若当前元素的next为空,则说明当前位置只有一个元素,无链表/红黑树,重新计算位置e.hash & (newCap - 1),放到新数组中即可
3.如果是红黑树结构,则拆分红黑树,必要时有可能退化成链表
4.如果是链表,则需要计算并判断当前位置的链表是否需要移动到新的位置
(1)如果当前元素的hash值和oldCap做与运算为0,则原位置不变,数组下标不变
(2)否则,需要移动到新的位置,数组下标为原下标 + 旧数组容量



