JUC1.8-ConcurrentHashMap源码学习-容量是2次幂以及如何保证是2次幂

前言

接着前面那篇 JUC1.8-ConcurrentHashMap源码学习-putVal()方法–“为什么在没有指定容量的时,默认容量时16呢?-----为什么容量都是的2的次幂呢?”中的问题展开进行分析解刨;

分析过程:

这个问题其实会衍生出来另一个问题: ConcurrentHashMap是怎么保证容量每次都是2的次幂呢?

看可以指定初始容量构造方法:

public ConcurrentHashMap(int initialCapacity) {
    i   f (initialCapacity < 0)
        throw new IllegalArgumentException();
    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
               MAXIMUM_CAPACITY :
               tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
    this.sizeCtl = cap;
}

逻辑很简单: 指定容量不能<0,在判断输入容量是否不小于最大的规定容量值的一半左右[MAXIMUM_CAPACITY >>> 1 这块说下为什么最大值是1 << 30 ,因为int有32位,这个位运算是int中最大的2的次幂数 ] 是赋值最大容量值,反则进行tableSizeFor方法构造,那么保证每次容量为2次幂就是在次方法中,来看下:

private static final int tableSizeFor(int c) {
    int n = c - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

这个时候,看不懂没关系,咱们用数据往里面代入,找规律好吧?
我们取 1,3,5,7,17,19,23,21,145,513,1023
在这里插入图片描述

发现了这个规则了么? 取大于当前数据离它最近的一个2次幂的数据,好厉害有木有?
用143具体做下位运算看下:

输入143
1. int n = c - 1;--------> 143 - 1 = 142
2. n |= n >>> 1;-------->1000 1110 | 0100 0111-------->1100 1111
3. n |= n >>> 2;-------->1100 1111 | 0011 0011-------->1111 1111
4. n |= n >>> 4;-------->1111 1111 | 0000 1111-------->1111 1111
5. n |= n >>> 8;-------->1111 1111 | 0000 0000-------->1111 1111
6. n |= n >>> 16;-------->0000 0000 1111 1111 | 0000 0000 0000 0000-------->0000 0000 1111 1111

有上可看出,其实到了n |= n >>> 2;时,值就已经确定了,不会在变了。 是不是还有奇怪的,为啥要n-1呢? 来看下最大值:1<<30

01 00000 00000 00000 00000 00000 00000 (n)   
01 10000 00000 00000 00000 00000 00000 (n |= n >>> 1)    
01 11100 00000 00000 00000 00000 00000 (n |= n >>> 2)    
01 11111 11000 00000 00000 00000 00000 (n |= n >>> 4)    
01 11111 11111 11111 00000 00000 00000 (n |= n >>> 8)    
01 11111 11111 11111 11111 11111 11111 (n |= n >>> 16) 

由于int类型为32位,所有即使除符号为之外只有第一位为1的情况,也能将所有的位全部变成1,不过由于最后计算出来为int类型的最大值,此时返回n+1会导致溢出,不能返回期望的结果,这也是为什么在方法开始是要执行int n = c - 1;的原因。

为什么源码在用initialCapacity + (initialCapacity >>> 1) + 1传入?

因为:在ConcurrentHashMap有一个参数LOAD_FACTOR,默认值为0.75f。假设当前map容量为16,当其中的元素个数达到16*0.75f,也就是12个的时候,map为了最大化利用hash的作用,会进行扩容,也就是map中的元素个数一般不会达到容量的大小。
使用参数initialCapacity + (initialCapacity >>> 1) + 1来设置容量,不至于在初始化时就超过上诉"12"这个元素,并且能提供一些多余的空间,不至于在插入元素后马上就进行比较耗时的扩容操作。

解释完了如何保证容量是2次幂,在看tab的取余与这个关系:

因为容量是2次幂,所以n-1后反码:肯定后面几位为1, 例如容量16时, 为0000 1111, 随意一个hash值 比如5 ,0000 0101 俩个用与运算 则为 0000 0101 等于5;

那么这样做 好处:
&运算速度快,至少比%取模运算块;
能保证 索引值 肯定在 capacity 中,不会超出数组长度;
最关键是(n - 1) & hash = hash % n 是必成立;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值