HashMap可以用通过空构造构造一个map,第一次扩容的大小默认是16。也可以传入一个扩容参数,HashMap会计算一个比大于等于这个扩容参数并且是2的次幂的容量作为第一次扩容时数组的大小。
那么HashMap在底层是怎么计算的呢?
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;
}
这里是计算上面所说的容量的方法。感觉很神奇,为啥通过这几行代码就能得到一个大于等于cap,且为2的次幂的数呢。
首先我们看2的次幂的数如:2、4、8、16等2的次幂的数有什么特点:
2 = 10
4 = 100
8 = 1000
16 = 10000
就是第最高位为1,其他位全为0
其次我们再看返回值,先进行判断,如果没问题就会返回一个 n+1,说明n本身不是一个2的次幂,n+1才是
那么n应该是一个2的次幂的的数减1:
2-1 = 1
4-1 = 11
8-1 = 111
16-1 = 1111
n这个数的特点也出来了,就是一个从全1的数。
由于我们传入的参数是cap,我们想找一个大于等于cap且是2的次幂的数,我们可以找到cap的最高位,这一位一定是1,然后我们让这一位之后的全部位为1,最后返回n+1即可。可是如果传进来的cap本身就是2的次幂。这个算法就会有问题,所以我们让n等cap-1,相当于cap的最高位会降低一位,最后再加1加回去。所以第一行代码是:
int n = cap - 1;
接下来的几行代码就是保证让n从最高位开始,每一位都为1
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
这是如何办的呢。我们随便写一个用32位bit表示的正整数:
0000 1011 0000 1011 1000 0100 0000 0100
让后进行上面的运算,我们只需盯着最高位的1即可
0000 1011 0000 1011 1000 0100 0000 0100
0000 0101 ...只看最高位的1,这里右移了一位,后面的不用看。取或运算的结果
由于第一位是1,它与0或1做或运算一定是1.
而第一位右移到第二位也是1,做或运算的结果一定是1
所以最高两位一定是两个1
第一次n |= n >>> 1 的结果一定是:
00...11...
前面是0占位符,后面不用管,最高位一定是两个1
同理,我把最高位向右移动两位,即把11移动到后面两位。再与自身做或运算
00...11...
00...11...
00...1111... 结果最高位一定是四个1
所以第三次是右移四位
后面同理右移8,16位
最中,从最高位到后面全是1
然后返回n+1,得到一个2的次幂。
为什么到16就可以呢,因为对于一个32位的整数移动 1+2+4+8+16 = 31
想右移动31位,去掉一位符号位,32位整数一共有31位数值位,所以移动到16就一定可以保证最高位开始,后面全为1了。