这是我参与「第四届青训营 」笔记创作活动的第3天
在学习redis时整好好奇,这几天就专门研究这玩意把
HashMap(无红黑树,因为不会)
扩容时机
在putval方法中出现了两次,还有一次出现在treeifyBin方法上
- put方法发现数组大小为0时调用resize进行初始化
- 当数组大小大于阈值时(阈值=容量*加载因子)
- 当前桶的元素数量大于8个,桶数量小于64
扩容原理
扩容大小为两倍
核心源码
这里去掉了第一次创建,和一些大小变化
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//扩容两倍
if (oldCap > 0) {
newThr = oldThr << 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;
if (e.next == null)
//重新计算hash,且旧桶为单个,不成链情况
newTab[e.hash & (newCap - 1)] = e;
//有子链,链分为高低位的头和尾
else {
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;
}
关键思想点
1、高低链
一次扩容就是两倍大小,rehash之后,就只可能出现在两个位置
如:17%16=1,33%16=1---扩容--->17%32 = 17,33%32 = 1
2、尾插法防止成环
详细参考BV1n541177Ea
jdk1.7之前是头插法
多线程的扩容下
- 两个线程同时进到transfer方法
2. 线程e1优先tranfer完
3. 线程e2唤醒后
- 最后当e2为2时,e2.next又是3,又将3赋值给了新桶的头元素,成环。e2指针从唤醒到结束:3--2--3