ConcurrentHashMap 扩容?lastRun 到底是个啥?,理解透彻
//lastRun 最终要处理的节点
Node<K, V> lastRun = f;// 尾节点,且和头节点的 hash 值取于不相等
//遍历当前 bucket 的链表,目的是尽量重用 Node 链表尾部的一部分
//从这里一直到下面 set 可以不用看,就是生成两个链,一个低位链,一个高位链
//下一个节点......下一个的下一个
//一条链上的 hash 值相等吗? 不相等 putval 的时候 (n-1)&hash 只是为了找到位置。
//这个 for 循环找到 lastRun,从 lastRun 开始后面的要在低位都在低位,要在高位都在高位
for (Node<K, V> p = f.next; p != null; p = p.next) {
// 取于桶中每个节点的 hash 值
//下一个节点的 hash&n 当前节点的 hash&n,因为一直循环所以就是尾结点和头结点的 hash&n 比较
int b = p.hash & n;
// 如果节点的 hash 值和首节点的 hash 值取于结果不同
if (b != runBit) {
runBit = b;// 更新 runBit 为尾结点的,用于下面判断 lastRun 该赋值给 ln 还是 hn。
lastRun = p;// 这个 lastRun 保证后面的节点与自己的取于值相同,避免后面没有必要的循环,因为上面是从 p 开始循环的。
// 所以这里到 lastrun 就不循环了
}
}
if (runBit == 0) {//如果最后更新的 runBit,设置低位节点
ln = lastRun;
hn = null;
} else {//否则,设置高位节点
hn = lastRun;// 如果最后更新的 runBit 是 1, 设置高位节点
ln = null;
}
//构造高位以及低位的链表
// 再次循环,生成两个链表,lastRun 作为停止条件,这样就是避免无谓的循环(lastRun 后面都是相同的取于结果)
// 将原本的一个链表根据 hash&n 分为 2 个链表,构建新链表采用头插法
// 无法概括两个新链表相对旧链表的顺序,有很多可能,并不是一个正序,一个倒序
// 这个 for 循环,把 lastRun 前面装配到高位节点或者低位节点
for (Node<K, V> p = f; p != lastRun; p = p.next) {
int ph = p.hash;
K pk = p.key;
V pv = p.val;
// 如果与运算结果是 0,那么就还在低位
if ((ph & n) == 0)// 如果是 0 ,那么创建低位节点
ln = new Node<K, V>(ph, pk, pv, ln);
else // 1 则创建高位
hn = new Node<K, V>(ph, pk, pv, hn);
}
setTabAt(nextTab, i, ln);//将低位的链表放在 i 位置也就是不动 低位链不需要变
setTabAt(nextTab, i + n,
hn);//将高位链表放在 i+n 位置,n 是数组的长度,是假如当前 14 14+16=30
setTabAt(tab, i, fwd);//把旧 table 的 hash 桶中放置转发节点,表明此 hash 桶已经被处理
advance = true;
}
===============================================================================
for (Node<K, V> p = f.next; p != null; p = p.next) {
// 取于桶中每个节点的 hash 值
//下一个节点的 hash&n 当前节点的 hash&n,因为一直循环所以就是尾结点和头结点的 hash&n 比较
int b = p.hash & n;
// 如果节点的 hash 值和首节点的 hash 值取于结果不同
if (b != runBit) {
runBit = b;// 更新 runBit 为尾结点的,用于下面判断 lastRun 该赋值给 ln 还是 hn。
lastRun = p;// 这个 lastRun 保证后面的节点与自己的取于值相同,避免后面没有必要的循环,因为上面是从 p 开始循环的。
// 所以这里到 lastrun 就不循环了
}
}
找到 lastRun,从 lastRun 开始后面的要在低位都在低位,要在高位都在高位
避免下面的 lastRun 又要反复的去计算一遍 hash 值(第二个循环中),如果 lastRun 后面很多的话,还是很客观的。
问题二:第二个 for 循环中为什么以 lastRun 作为结束标志?
=============================================================================================
问题一中给了答案,因为 lastRun 后面 runBit 是一样的,不需要单独进入 for 循环判断到底放在低位链,还是高位链。
问题三:lastRun 到底十个什么?为什么不用在第二个循环里放?我不放的话,在哪里把 lastRun 后面的放到低位链或者高位链?
=============================================================================================================================
图片来自:https://blog.csdn.net/ZOKEKAI/article/details/90051567
在第一步中就有了答案,下面这两个代码直接把 lastRun 当做高位链或者低位链的开始,前面的再采用 for 循环慢慢往里插(头插法)
因为链表中的 node 有 next,lastRun 变成高位链或者低位链的时候待着后面的弟兄们就都过去了,后面再头插法不就连起来了?我真是个呆瓜。
if (runBit == 0) {//如果最后更新的 runBit,设置低位节点
ln = lastRun;
hn = null;
} else {//否则,设置高位节点
hn = lastRun;// 如果最后更新的 runBit 是 1, 设置高位节点
ln = null;
}
================================================================
分为三步:
第一个 for 循环:找到 lastrun
两个 if:lastrun 节点变为高低位链的初始节点
第二个 for 循环:采用头插法往 lastRun 前面插,遍历到 lastRun 结束。
评论