JUC1.8-ConcurrentHashMap源码学习-为什么每次扩容是原来两倍?

前言

这个问题是扩容里面的一个遗留问题,前因后果大家可以去查阅上篇博客JUC1.8-ConcurrentHashMap源码学习-扩容方法解析transfer() ,在讲扩容机制时,篇幅过长,因此单独拿出来说明;

大家看过源码或者是读过笔者扩容那篇博客,就知道在每次扩容时,均在原容器的基础上,扩大2倍,贴下transfer()源码里面的位置:

if (nextTab == null) {            // 开始初始化
        try {
            //n << 1 相当于2 * n 也就是2倍的n
            @SuppressWarnings("unchecked")
            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
            nextTab = nt;
        } catch (Throwable ex) {      // 防止内存不够,出现OOM
            sizeCtl = Integer.MAX_VALUE;
            return;
        }
        //将生产好的2倍node数组,更新到成员变量
        nextTable = nextTab;
        //更新转移下标, n 就是老桶的长度
        transferIndex = n;
    }

Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; 在代码也注释了,n代表原长度 左移1位,相当于2 * n, 不明白的可以查阅JUC1.8-ConcurrentHashMap源码学习-准备中的Java位运算部分。 so这篇我们就好好分析分析。

当笔者在遇到这种 莫名的关系时,我都会采用具体数字代入的方式,先找到有木有啥有规律的规则,然后 在联动该方法上下文进行分析,那么我就先带着大家用数据代入的方式看下:

总所周知的:在往容器put数据时,都得根据当前key值得hashcode取余n,然后找到对应的tab的下标位置,那么也可能发生hash碰撞后,就通过链表,或者红黑树将新的key放到旧的key后面。 例如:
现有key: “name”,“age”,“email”,“phone”,当前的哈希表容量是8

    1. 通过java的hashCode() 函数可以计算出哈希码
    
    2. 计算对应tab的下标 , (n-1) & hash 
     “name”字符串的哈希码是3373707(十进制),1100110111101010001011(二进制) & 00000111 = 0011,下标:3
     
     “phone”字符串的哈希码是194811(十进制),101111100011110011(二进制)& 00000111 = 0011,下标:3

     “age”字符串的哈希码是96511(十进制),       10111100011111111(二进制)& 00000111 = 0111,下标:7
     
     “email”字符串的哈希码是96619420(十进制),101110000100100101110011100(二进制)& 00000111 = 0100,下标:4

    
     

所以目前对应的结构是:
01234567
nameemailage
phone
3.此时来扩容,n << 1 新tab 长度就是16,
重新再来计算新的下标,这里就拿name和phone为例:

    “name”字符串的哈希码是3373707(十进制),1100110111101010001011 & 00001111 = 1011,下标:11
     
    “phone”字符串的哈希码是194811(十进制),101111100011110011 & 00001111 = 0011,下标:3

从这就可以看出来,

name的与7运算结果 是 0011 ,与15运算结果是1011 发现首位高一位,所以离开原有的节点, 那么新的位置 正好就是 原下标 + 原tab长度 = 3 + 8 = 11;

phone的与7运算结果 是 0011 ,与15运算结果是0011 发现首位没有变化,so所有并没变化下标值。

这就是扩大一倍的好处:

当初容量是8,8-1=7,7的二进制是111;
现在容量是16,16-1=15,15的二进制是1111

因为他们都是全为1的形态,而扩大一倍后只是高位加了一个1,所以会留有大概一半的key在原来的空格,而有一半的key到相同的别的空格中,比如都是从3号位置到了11号位置中,这样也便于复制。通过位运算,代替了模运算!

总结

总结下:扩大2倍 = n<<1 也就相当于原长度的左移一位,也就是高位加了1, 那么在去做与运算时,在同一个链表的下面的key,如果高位也加1了,说明这个节点的对应的tab的下标也得跟着加上扩长的长度,俗称高位桶, 反正还是为0说明高位没有变化,俗称低位桶,放在原位置不动即可。 所以在扩容的时候,int runBit = fh & n; 用于来得到属于高位还是低位, 如果是低位下标不变,否者下标直接加就好了,就不用在重新计算新容器的取余计算了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值