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


这一小段看了两个小时!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后面的放到低位链或者高位链?

在这里插入图片描述

在这里插入图片描述
图片来自: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结束。

连接

图解CHM:
https://blog.csdn.net/ZOKEKAI/article/details/90051567

不错的注释:
https://www.cnblogs.com/bosslv/p/11310546.html
https://www.jianshu.com/p/2829fe36a8dd

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付 9.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值