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<K,V>[] resize() {
Node<K,V>[] 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<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> 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<K,V>)e).split(this, newTab, j, oldCap);
// 4
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> 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<K,V>[] 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)否则,需要移动到新的位置,数组下标为原下标 + 旧数组容量