在 Java8 中,HashMap 由数组+链表+红黑树组成的。扩容时,数组容量翻倍,数组中每一个桶中的多个节点(链结构或树结构)都需要 rehash 迁移到新的数组中去。
本文通过阅读 HashMap 的 resize 方法了解其扩容原理,对桶节点的迁移算法进行单元测试,画图以方便理解。
1. 扩容的时机HashMap 中 put 入第一个元素,初始化数组 table。
HashMap 中的元素数量大于阈值 threshold。threshold = capacity * load factor。
当 size > threshold 时,触发 rehash。
2. 扩容的源码
HashMap 中的 resize 方法主要包含两部分逻辑:初始化数组 table,并设置阈值。
数组容量翻倍,将元素迁移到新数组。/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
final Node[] resize() {
Node[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) { // 第一次进来,table为null,oldCap为0,不会进入这里
if (oldCap >= MAXIMUM_CAPACITY) { // 扩容前的数组大小如果已经达到最大(2^30)了
threshold = Integer.MAX_VALUE; // 取整型最大值(2^31-1),这样以后就不会扩容了
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && // oldCap翻倍得到newCap
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold // 第一次进来,如果手动设置了初始容量initialCapacity,这里为true,则将threshold作为初始容量
newCap = oldThr;
else { // zero initial threshold signifies using defaults // 如果没有手动设置initialCapacity,则设为默认值16
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) { // 第一次进来,这里必为true,重新计算 threshold = capacity * Load factor
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node[] newTab = (Node[])new Node[newCap];
table = newTab;
if (o