HashMap中的算法魅力之初始容量计算
懒人先看:HashMap用翻倍位移
低位补1算法
实现了初始容量计算。
转载请注明出处,您的支持是我坚持不懈的动力源泉~
初始化容量的计算
- 找到比当前数值大的最小2次幂,入参cap是二进制的用户输入的容量;
static final int tableSizeFor(int cap) {
int n = cap - 1;//为啥要减去1?cap代表的是数量,减1后就能知道存储对应cap数量二进制所需的位数
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、2、4、8、16位呢?
hash值是int类型的,长度为32。我们举个例子:1010 1100 1110 0010
1、向右位移1位,则可以保证原来不为0最高位的相邻位为1,或运算后在保留最高位的前提下,把次高位也变成了1。第一步保证了不为0的前2位或运算结果为1。
2、接下来,位移2位,可以保证前4位有效位全为1;
3、接下来,位移4位,可以保证前8位有效位全为1;
4、再次位移16位,最终保证最高位向右的位全部为1。
图示:
结果再加上数值0,就是初始化容量的最大值了。
这个算法是不是还能优化呢?
我们可以看到,低位参与的或运算的结果并不算有效计算,因为最终结果被舍弃了,即位移过程中有最高位就够了。我在这里斗胆把它称作为低位补1算法
。那我们是不是发现了该算法还有优化的余地呢?恰巧,我在阅读ConcurrentHashMap
源码的时候,看到了这样一个算法:
int ssize = 1;
while (ssize < DEFAULT_CONCURRENCY_LEVEL) {
++sshift;
ssize <<= 1;
}
应用在HashMap这里是不是可以把位移这块代码改成这样呢?
static final int tableSizeFor(int cap) {
int ssize = 1;
while (ssize < cap) {
ssize <<= 1;
}
}
嘻嘻一阵窃喜,有木有~
然而,并非如此……
接下来我们把这两种算法分别叫做“原生低位补1算法”、“比较位移算法”,二者的区别其实很明显了。原生算法不管用户的入参cap值有多大,仍然进行了5次位移或运算;比较位移算法则从小到大逐位位移,如果cap值很大,位移+比较的次数就会很大了!也就是值小的时候可以采用第二种算法。
最大容量的计算
我们再看一下HashMap是怎么定义最大容量的?
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
二进制表示为:0100000000000000000000000000000
,也就是2^30。
请注意,容量的返回值是int类型,按照第一节中提到的算法,最高位起低位补1的结果+1就是初始容量
!基于此算法,我们的最高位是不是就不能为1了呢,否则就会造成数值溢出!
总结
HashMap的容量计算用到的是低位补1算法
,并巧妙的用到了翻倍位移。