在 Java8 中,HashMap 由数组+链表+红黑树组成的。
扩容时,数组容量翻倍,数组中每一个桶中的多个节点(链结构或树结构)都需要 rehash 迁移到新的数组中去。
本文通过阅读 JDK8 中 HashMap 的 resize 方法了解其扩容原理,对桶节点的迁移算法进行单元测试,画图以方便理解。
本文基于 jdk1.8.0_91
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<K,V>[] resize() {
Node<K,V>[] 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<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 对oldTab中所有元素进行rehash。由于每次扩容是2次幂的扩展(指数组长度/桶数量扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
// 数组j位置的元素不为空,需要该位置上的所有元素进行rehash
oldTab[j] = null;
if (e.next == null) // 桶中只有一个元素,则直接rehash
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode

本文详细探讨了Java8中HashMap的扩容原理,包括扩容的触发时机、resize方法的源码分析以及链表和树结构在扩容时的迁移算法。通过单元测试和图解方式,展示了扩容过程中节点如何按照新索引分布,确保元素顺序和数据一致性。
最低0.47元/天 解锁文章
2267

被折叠的 条评论
为什么被折叠?



