HashMap的指定初始容量的构造函数:
public HashMap(int initialCapacity, float loadFactor)
初始容量是根据参数 initialCapacity,求出 大于等于 initialCapacity 的最小的 2的N次方。
容量是2的N次方的原因,可参见 http://www.cnblogs.com/xwdreamer/archive/2012/06/03/2532832.html
1、在JDK低版本中,通过循环移位运算,保证了初始容量为2的N次方
public HashMap(int initialCapacity, float loadFactor) { ...... int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; ...... }
2、JDK1.8中,对该运算进行了优化
public HashMap(int initialCapacity, float loadFactor) { ...... this.threshold = tableSizeFor(initialCapacity); } 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; }
2.1 参数不是2的N次方,转为二进制
1xxxxxxxx (假设9位,x中至少有一个为1),大于等于该数的最小2的N次方如下,十位
1000000000
第一步 1xxxxxxxx - 1,由于低位至少有一个1,所以减1后,位数不变
第二步 n |= n >>> 1; 1xxxxxxxx 右移一位 变成 1xxxxxxx, 或运算后变为 11xxxxxxx ,不管低位
第三步 n |= n >>> 2; 11xxxxxxx 右移两位 变成 11xxxxx, 或运算后变为 1111xxxxx ,不管低位
第四步 n |= n >>> 4; 1111xxxxx 右移四位 变成 1111x, 或运算后变为 11111111x ,不管低位
......
最终能保证所有低位上都是1: 1xxxxxxxx -> 111111111 。 +1 变为 1000000000 ,是大于等于该数的最小2的N次方
右移或运算,截止到16,能保证最多32位上都是1,是因为int型的最大值231-1,是31位
2.2 参数是2的N次方
举例 1000, n-1后变为 111,右移或运算后还是 111, +1后变为 1000
3、比较
低版本中,假设传参为2的N次方,比较 + 位移,一共计算了 2 * N 次
JDK1.8中,减法 + 位移 + 或运算,大概计算 11 次
也就是说,指定数组容量大于 2的6次方(64)后,JDK1.8的效率更高
4、传参恰好为2的N次方时的优化
如果一个数n,其不为1,且n-1 & n = 0,那么n就是一个2的整数次幂
JDK1.8里加这个判断可以减少计算
static final int tableSizeFor(int cap) { int n = cap - 1; if(cap & n == 0) // 传参为2的N次方 return (cap >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : 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; }