HashMap和ConcurrentHashMap中的initialCapacity

HashMap 默认有一个初始大小(initialCapacity),这个初始大小是16。
在各种开发规范手册中都可以看到会建议设置这个大小。
例如:new HashMap(3);
那么我们设置了初始容量为3,HashMap的容量真的会初始化为3了吗?
答案是否定的。
为了提高Hash效率,Java中会重新计算这个值,获得一个>3的最小2的N次方,大于3的最小2的N次方就是4(关于为什么请参照
https://www.zhihu.com/question/28562088/answer/111668116)。
这个4是怎么来的呢?

1
2
3
4
5
6
7
int n = cap - 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;

嗯哼?第一眼看到觉得莫名奇妙,仔细看看,就会很佩服了!
这里主要用到了两个运算,位移运算(向右无符号,高位补零)和或运算。
例如:cap=3,套入上面的公式,如下:

1
2
3
4
int n = 3-1;
//第一步位移运算转换为二进制为:10>>>1 = 01
//第二部或运算转换为二进制为:10|=01 = 11
n|=n>>>1;

最终n=11(二进制)最后加上1 = 100(二进制)
转换成10进制就是4啦!
需要注意的是:cap-1,这是因为,如果cap本身就是2的次方,套用上面的公式得出来的结果就不是最小的2的n次方了。

下面来说ConcurrentHashMap中的最小2的n次方技巧。应该都知道ConcurrentHashMap是通过分段锁来提高效率的,其中一个segment就是一个锁。但是初始化的时候初始segment数组多大合适呢?
segment大小跟concurrencyLevel有关,求的是大于concurencyLevel的最小2的n次方。

1
2
3
4
5
 int ssize = 1;
while (ssize < concurrencyLevel) {
++ sshift;
ssize <<= 1;
}

这里用到的是位移运算(向左)。
例如concurrencyLevel=5,
第一次
ssize<<=1 结果:10(二进制),2(10进制)
第二次
ssize<<=1 结果:100(二进制),4(10进制)
第三次
ssize<<=1 结果:1000(二进制),8(10进制)
这时结果已经>concurrencyLevel,while结束,于是最终结果就是8.
所以最终segment 数组大小会被初始化为8.

HashMap和ConcurrentHashMap中的initialCapacity

https://jingzhouzhao.github.io/archives/5536c54e.html

作者

太阳当空赵先生

发布于

2018-07-24

更新于

2022-02-22

许可协议

评论