扩容前计算索引 | |
---|---|
1010 0101 | |
& | 0000 1111 |
0000 0101 | |
索引结果 | 5 |
扩容以后容量是n=32(对应的二进制是0001 1111),node本身的hash值是不变的,仍然是1010 0101,那么扩容后node 的索引的计算是通过如下方式得到
扩容后计算索引 | |
---|---|
1010 0101 | |
& | 0001 1111 |
0000 0101 | |
索引结果 | 5 |
发现计算后的索引结果和扩容前是一样的,那么是什么原因导致扩容前后的索引是一样的?或者是不一样的呢?我们把扩容前后的两次hash计算索引的过程放在一起对比一下
扩容前计算索引 | 扩容后计算索引 | ||
---|---|---|---|
(hash) | 1010 0101 | 101 0 0101 | |
(n-1)& | 0000 1111 | & | 000 1 1111 |
0000 0101 | 0000 0101 | ||
索引结果 | 5 | 5 |
先给出结论,注意看一下(n-1) 的值他转化为二进制以后的第五位,也就是表中加粗的数字
扩容前计算索引 | 扩容后计算索引 | ||
---|---|---|---|
(hash) | 1011 0101 | 101 1 0101 | |
&(n-1) | 0000 1111 | & | 000 1 1111 |
0001 0101 | 000 1 0101 | ||
索引结果 | 5 | 16 + 5 |
我们发现,最后扩容以后计算结果中第5位变成了1,低四位不变,也就是16 + 5,而这增加的16恰好是map扩容前的容量16,回到上面的问题,node的hash值的二进制如果第5位是1,扩容后的索引就是 扩容前的容量 + 原索引值。
hash值二进制第五位是1 | hash值二进制第五位是0 | ||
---|---|---|---|
1011 0101 | 101 0 0101 | ||
& | 0001 0000 | & | 000 1 0000 |
0001 0000 | 000 0 0000 | ||
第五位结果 | 1 | 0 |
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//首次初始化后table为Null
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;//默认构造器的情况下为0
int newCap, newThr = 0;
if (oldCap > 0) {//table扩容过
//当前table容量大于最大值得时候返回当前table
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//table的容量乘以2,threshold的值也乘以2
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
//使用带有初始容量的构造器时,table容量为初始化得到的threshold
newCap = oldThr;
else { //默认构造器下进行扩容
// zero initial threshold signifies using defaults
newCap = 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"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
HashMap.Node<K,V> e;
if ((e = oldTab[j]) != null) {
// help gc
oldTab[j] = null;
if (e.next == null)
// 当前index没有发生hash冲突,直接对2取模,即移位运算hash &(2^n -1)
// 扩容都是按照2的幂次方扩容,因此newCap = 2^n
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof HashMap.TreeNode)
// 当前index对应的节点为红黑树,这里篇幅比较长且需要了解其数据结构跟算法,因此不进行详解,当树的个数小于等于UNTREEIFY_THRESHOLD则转成链表
((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
// 把当前index对应的链表分成两个链表,减少扩容的迁移量
HashMap.Node<K,V> loHead = null, loTail = null;
HashMap.Node<K,V> hiHead = null, hiTail = null;
HashMap.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) {
// help gc
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
// help gc
hiTail.next = null;
// 扩容长度为当前index位置+旧的容量
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
参考
https://www.jianshu.com/p/321fdf485970
https://blog.csdn.net/u010890358/article/details/80496144#扩容机制核心方法Node%3CK%2CV%3E%5B%5D%20resize()