最近在看一些集合的源码,看到HashMap感觉用到了很多移位运算的技巧,看了好一会儿才明白,记一下以免以后再忘了
后面有时间会整理完整的源码
HashMap中有这样一段代码,当初始化HashMap时,如果指定了初始容量initialCapacity,由于哈希桶的数目必须是2的n次幂,因此要把initialCapacity转化为比它大的最小的2的n次幂数,例如initialCapacity = 10 ,那就返回16, initialCapacity = 17,那么就返回32。
/**
* 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;
}
以上代码就是转换的代码,首先考虑一下如果我要实现这样一个转换的思路:首先通过一个循环去判断int值的二进制表示中,最高位的1是第几位,然后返回一个最高位+1的的2的幂数,这样需要循环31次,代码如下:
public static int convert(int initialCapacity) {
int a = 1 ;
int ans = 1;
int c = initialCapacity - 1 ; //减1的目的是避免当initialCapacity = 2^n时,返回结果2^n+1
for(int i=1;i<31;i++) {
a = a << 1;
if((c & a) > 0)
ans = i;
}
return 1 << (ans + 1);
}
jdk源码的做法是通过5次无符号移位运算以及或运算就得到了这个值,分析一下做法:
n第一次右移一位时,相当于将最高位的1右移一位,再和原来的n取或,就将最高位和次高位都变成1,也就是两个1;
第二次右移两位时,将最高的两个1向右移了两位,取或后得到四个1;
依次类推,右移16位再取或就能得到32个1;
最后通过加一进位得到2^n 。
注意,当initialCapacity本身就是2^n时,结果会是2^n+1,为了避免出现这样的情况,在一开始将initialCapacity-1,只要该数不是2^n(10000....),减1不会影响最高位1的位置。
举例说明:
例1
10的二进制是1010,减1就是1001
第一次右移取或: 1001 | 0100 = 1101 ;
第二次右移取或: 1101 | 0011 = 1111 ;
第三次右移取或: 1111 | 0000 = 1111 ;
第四次第五次同理
最后得到 n = 1111 ,返回值是 n+1 = 2 ^ 4 = 16 ;
例2
16的二进制是10000,减1变为01111,移位取或运算之后,返回的就是16
如果不减1,输入10000,移位取或运算之后得到的是11111,返回的是32