问题背景 HashMap源代码里面有这样一个方法,计算一个整数向上最接近的2指数,其实现过程如下,可以说是非常巧妙。
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
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;
}
以上代码的逻辑实现过程不是很明显,但是换一种思路来理解之后,便豁然开朗。
二进制形式 首先来看一个整数的二进制表示形式,为了简化说明过程,使用8位整数,Java语言的32位整形同样适用:
0100 1101
要获取这个整数的最接近的2指数,只需要找到其最高位所在位置,然后在该位基础上往左一位,得到结果为:
1000 0000
根据上述原则,以下计算可以非常快速的出结果:
0010 1111 --> 0100 0000
0010 1001 --> 0100 0000
0010 0001 --> 0100 0000
0001 1111 --> 0010 0000
0001 1011 --> 0010 0000
...
但是,有一种情况例外,即当该整数原本就是2的指数时,那么其本身就是最接近的2指数,即: 0001 0000 --> 0001 0000 1000 0000 --> 1000 0000 0000 0001 --> 0000 0001 ...
为了协调这种情况,一个小技巧就是,直接将原来的数减1,然后采用之前的方法:
0001 0000 --> 0000 1111 --> 0001 0000
1000 0000 --> 0111 1111 --> 1000 0000
0100 1111 --> 0100 1110 --> 1000 0000
0010 0001 --> 0010 0000 --> 0100 0000
...
代码实现逻辑 有了这个思路之后,代码实现需要解决的问题就是,如何定位到最高位1所在位置,hashmap中的方法非常巧妙,通过采用右移位的方式,实现了这种目的。
// 待计算的数值(79)为:
0100 1111
// 1、先减去1 :int n = cap - 1;
0100 1110
// 2、右移1位后,与自身进行位或运算:n |= n >>> 1;
0100 1110
010 0111
011X XXXX //此处部分位置结果用X表示,是因为其取值对最终结果没有影响,为了方便理解
// 右移2位后,与自身进行位或运算:n |= n >>> 2;
011X XXXX
001 1XXX
0111 1XXX //此处部分位置结果用X表示,是因为其取值对最终结果没有影响,为了方便理解,
// 右移4位后,与自身进行位或运算:n |= n >>> 2;
0111 1XXX
000 0111
0111 1111
// 3、最终结果加1,得到结果为128
其他实现方式 除了上述方法之外,也可以采用如下方式:
static final int tableSizeFor2(int cap) {
int carry = 0;
for (int c = cap - 1; c > 0; c >>>= 1) {
carry++;
}
return cap == 0 ? 1 : (1 << (carry));
}