ConcurrentHashMap扩容?lastRun到底是个啥?(普通链表)我真是个呆瓜!

68 阅读6分钟

这一小段看了两个小时!CHM看到如下这段代码彻底给我干蒙了,我是真的菜,感觉自己没有智商~

这段代码是CHM扩容中的普通链表扩容,看完之后整个人都不一样了,Doug Lea JUC包中的任何一行代码简直都是教科书式的操作,要仔细回味,感觉自己就像古代的穷书生读到了大文豪的一篇白话文。

人与人的智商确实不一样吗?呜呜呜,笨就要努力点呀!阿西

请忽略我一堆小儿科的注释。

//处理链表

if (tabAt(tab, i) == f) {//再做一次校验

//一个高位节点,一个低位节点

/玄机/

// /为什么这么做?

// 1. 一个一个节点迁移,需要计算很多次这个点hash值,有了高低位,只需要在这里进行一次计算(下面的第二个for循环只需要计算一次),且迁移也是一次(lastRun)

// 2. 刚开始putval的时候(f = tabAt(tab, i = (n - 1) & hash)) == null,通过例子发现

// 对于同一个hash值,n=16和n=32这个式子计算出来是不一样的,而中间正好相差n,就是数组的长度,所以这里把高位链添加到i+n的地方

// 下次获取的时候相当于一下就获取到了低位链和高伟链的数据

Node<K, V> ln, hn;//1n表示低位,hn表示高位;接下来这段代码的作用是把链表拆分成两部分,0在低位,1在高位

if (fh >= 0) {

// 高低位分开总结

// 当前节点fh&n != 尾结点fh&n 就用尾结点的

//所以就是尾结点的第x位等于0? 0->低位 否则->高位

//fh往上面上面else if ((fh = f.hash) == MOVED)进行了赋值,就是当前节点的哈希值

//n tab长度

//把当前链表分成两类

//a类节点哈希值的第x位等于0(fh & n)

//b类节点哈希值的第x位不等于0(fh & n)

int runBit = fh & n;

//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循环什么意思?

===============================================================================

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后面的放到低位链或者高位链?

=============================================================================================================================

在这里插入图片描述

在这里插入图片描述

图片来自:blog.csdn.net/ZOKEKAI/art…

在第一步中就有了答案,下面这两个代码直接把lastRun当做高位链或者低位链的开始,前面的再采用for循环慢慢往里插(头插法)