HashMap:为什么容量总是为2的n次幂
1)、HashMap是根据key的hash值决定key放到哪个桶中,通过tab[i = (n - 1) & hash]公式计算得出
这里的n是HashMap的容量,因为n永远是2的次幂,所以n - 1通过二进制表示,永远都是末尾连续1的形式表示(如00001111、00000011),当(n - 1) 和hash做与运算时,会保留hash中后x位的1
例如00001111 & 10000011 = 00000011
这样做的好处在于:
-
&运算速度快,至少比%取模运算快
-
能保证索引值肯定在HashMap的容量大小范围内
-
(n - 1) & hash的值是均匀分布的,可以减少hash冲突
举个例子来看:
假设n = 16,hash从0开始递增:
hash | (n-1)& hash | 结果 |
---|---|---|
0 | 1111 & 0 | 0 |
1 | 1111 & 1 | 1 |
2 | 1111 & 10 | 2 |
3 | 1111 & 11 | 3 |
4 | 1111 & 100 | 4 |
5 | 1111 & 101 | 5 |
…… | …… | …… |
16 | 1111 & 10000 | 0 |
17 | 1111 & 10001 | 1 |
18 | 1111 & 10010 | 2 |
假设n = 15,hash从0开始递增:
hash | (n-1)& hash | 结果 |
---|---|---|
0 | 1110 & 0 | 0 |
1 | 1110 & 1 | 0 |
2 | 1110 & 10 | 2 |
3 | 1110 & 11 | 2 |
4 | 1110 & 100 | 4 |
5 | 1110 & 101 | 4 |
…… | …… | …… |
16 | 1110 & 10000 | 0 |
17 | 1110 & 10001 | 0 |
18 | 1110 & 10010 | 2 |
2)、那么问题来了,如果我们通过构造函数指定了initialCapacity不为2的n次幂时,是不是就破坏了这个规则?
答案是不会的,HashMap的tableSizeFor方法做了处理,能保证n永远都是2的n次幂,让我们通过源码来看:
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
// 调用了tableSizeFor方法来指定桶大小
this.threshold = tableSizeFor(initialCapacity);
}
static final int tableSizeFor(int cap) {
// cap-1后,n的二进制最右一位肯定和cap的最右一位不同,即一个为0,一个为1,例如cap=17(00010001),n=cap-1=16(00010000)
int n = cap - 1;
// n = (00010000 | 00001000) = 00011000
n |= n >>> 1;
// n = (00011000 | 00000110) = 00011110
n |= n >>> 2;
// n = (00011110 | 00000001) = 00011111
n |= n >>> 4;
// n = (00011111 | 00000000) = 00011111
n |= n >>> 8;
// n = (00011111 | 00000000) = 00011111
n |= n >>> 16;
// n = 00011111 = 31
// n = 31 + 1 = 32, 即最终的cap = 32 = 2 的 (n=5)次方
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
3)、hash算法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashMap中的hash也做了比较特别的处理,(h = key.hashCode()) ^ (h >>> 16)
先获得key的hashCode的值 h,然后 h 和 h右移16位 做异或运算
实质上是把一个数的低16位与他的高16位做异或运算,因为在前面 (n - 1) & hash 的计算中,hash变量只有末x位会参与到运算。使高16位也参与到hash的运算能减少冲突
例如1000000的二进制是 00000000 00001111 01000010 01000000
右移16位: 00000000 00000000 00000000 00001111
异或 00000000 00001111 01000010 01001111