我们知道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
}
结论(两种计算方法,不推荐第二种):
-
ConcurrentHashMap初始化设置容量时,底层会自动转换为距设置值2倍的最近的2的次幂;
【前后距离相等时选择向后转换:如3 * 2 = 6距离4和8相等,选择向后转换为8】 -
结果总是为初始值加它本身的一半(向下取整)再+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;
}
}