手动设置ConcurrentHashMap初始化值会变成多少?详细计算过程

我们知道ConcurrentHashMap的初始化值都会变成2的整数次幂,但是手动设置初始容量具体变成多少该怎么计算呢?例如初始化输入2、4、8 、 14 、16,其容量会变成多少呢?


假设分别输入初始化值1/2/3/5/8/9/14/17

然鹅得到对应初始容量为:

//1 --> 2
//2 --> 4
//3 --> 8
//5 --> 8
//7 --> 16
//8 --> 16
//9 --> 16
//14 --> 32
//17-->  32

一下子看是不是很疑惑看不到规律?特别是7和14这两个值,不是说好是2的整次幂吗,那么容量不应该为8或者16就好了嘛???难道我被诓了吗?

不急不急,我们进入ConcurrentHashMap里面康康它是怎么计算的:

//进来看到它的构造方法如下:
//initialCapacity就是我们输入的初始化值
    public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }

可以看到,最终计算的式子是一个三目运算,然后将得到的cap赋值给sizeCtl:

int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
                   
this.sizeCtl = cap;                

里面的MAXIMUM_CAPACITY>>>tableSizeFor都是些啥丫?别急,下面告诉你

MAXIMUM_CAPACITY是ConcurrentHashMap容量的最大值,在源码中:

 /**
     * The largest possible table capacity.  This value must be
     * exactly 1<<30 to stay within Java array allocation and indexing
     * bounds for power of two table sizes, and is further required
     * because the top two bits of 32bit hash fields are used for
     * control purposes.
     */
    private static final int MAXIMUM_CAPACITY = 1 << 30;
    

1 << 30 = 1,073,741,824

java中>>>意思是无符号逻辑右移,先来解释一下>>、>>>的区别:

  • >>表示右移,如果该数为正,则高位补0,若为负数,则高位补1;

    例如:

    • 20 >> 2

      20的二进制补码:0001 0100
      向右移动两位后:0000 0101
      结果:=5
    • -20 >> 2 【省略原码转补码过程,不懂另外百度】

      -20的二进制原码:1001 0100
      向右移动两位后:1000 0101
      结果:=-1
  • >>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0

    • 正数:20 >>> 2,结果与 r = 20 >> 2 相同;

    • 负数:-20 >>> 2

      数据类型假设为int 32位

      源码:10000000 00000000 00000000 00010100
      反码:11111111 11111111 11111111 11101011
      补码:11111111 11111111 11111111 11101100
      右移:00111111 11111111 11111111 11111011
      结果:r = 1073741819

参考Java中的<< 和 >> 和 >>> 详细分析

那么现在记住>>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0就好啦

tableSizeFor又是个啥方法呢?进去看一下:

 /**
     * Returns a power of two table size for the given desired capacity.
     * See Hackers Delight, sec 3.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;
    }

好了,现在该知道的都了解完了,那么开始一步步分析它初始值的变化:

回来看构造方法,假设我手动输入的值为7:

 public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)    //7
            throw new IllegalArgumentException();
            //(initialCapacity >= (MAXIMUM_CAPACITY >>> 1)意思大概理解为当手动设置的容量超过了最大容量值,则去最大值,这个没什么好说的
            //那么如果没有超过,则进入tableSizeFor方法对initialCapacity进行计算
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        
        this.sizeCtl = cap;
    }

initialCapacity + (initialCapacity >>> 1) + 1)传进tableSizeFor中:

private static final int tableSizeFor(int c) {
//因为在刚进入构造方法时就对初始值进行小于零判断,故走到这一步的初始值都是正整数
//那么c=initialCapacity + (initialCapacity >>> 1) + 1) 便可以口头理解为初始值加它本身的一半(向下取整)再+1【有点废话,主要便于记忆】
//  c=7 + (7>>>1) +1= 11 -->    (1011)

        int n = c - 1;  //11-1=10   (1010)
        n |= n >>> 1;   //(1010 | 1010 >>> 1) = (1010|0101)=(1111)
        n |= n >>> 2;   //(1111 | 1111 >>> 2) = (1111|0011)=(1111)
        n |= n >>> 4;   //(1111 | 1111 >>> 4) = (1111|0000)=(1111)
        n |= n >>> 8;   //(1111 | 1111 >>> 8) = (1111|0000)=(1111)
        n |= n >>> 16;  //(1111 | 1111 >>> 16) = (1111|0000)=(1111)
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; //return (1111)+1 = (1 0000) =16
    }

结论(两种计算方法,不推荐第二种):

  1. ConcurrentHashMap初始化设置容量时,底层会自动转换为距设置值2倍的最近的2的次幂;
    【前后距离相等时选择向后转换:如3 * 2 = 6距离4和8相等,选择向后转换为8】

  2. 结果总是为初始值加它本身的一半(向下取整)再+1后转化为二进制的值,最高位的1左移1位,其余为全为零的值【如7+(int)7/2+1=13(1010) -->左移且补零(10000)=16】

其余的值可以自行计算,或者用我的测试代码:

package com.yq;

//ConcurrentHashMap计算初始化大小
public class MyConcurrentMap  {
   static final int MAXIMUM_CAPACITY = 1 << 30;


    public static void main(String[] args) {
        int initialCapacity = 8;
        System.out.println(Integer.toBinaryString(initialCapacity));
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                MAXIMUM_CAPACITY :
                tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));

        System.out.println(cap);
    }

   static int tableSizeFor(int c) {
        int n = c - 1;
       System.out.println(" n = c - 1:"+Integer.toBinaryString(n));
       System.out.println(" n >>> 1:"+Integer.toBinaryString(n >>> 1));
        n |= n >>> 1;
       System.out.println("n |= n >>> 1:"+Integer.toBinaryString(n));
       System.out.println(" n >>> 2:"+Integer.toBinaryString(n >>> 2));
       n |= n >>> 2;
       System.out.println(" n |= n >>> 2:"+Integer.toBinaryString(n));
       System.out.println(" n >>> 4:"+Integer.toBinaryString(n >>> 4));
       n |= n >>> 4;
       System.out.println("  n |= n >>> 4:"+Integer.toBinaryString(n));
       System.out.println(" n >>> 8:"+Integer.toBinaryString(n >>> 8));
       n |= n >>> 8;
       System.out.println(" n |= n >>> 8:"+Integer.toBinaryString(n));
       System.out.println(" n >>> 16:"+Integer.toBinaryString(n >>> 16));
       n |= n >>> 16;
       System.out.println(" n |= n >>> 16:"+Integer.toBinaryString(n));
       System.out.println(" n +1:"+Integer.toBinaryString(n+1));
       return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值