1.为什么Entry数组大小必须为2的幂
// JDK 1.7
static int indexFor(int h, int length) {
return h & (length-1);
}
- 使用减法替代取模,提升计算效率;
- 为了使不同 hash 值发生碰撞的概率更小,尽可能促使元素在哈希表中均匀地散列。
我们知道,在计算机中,0与任何值相&都是0;1与任何值相&则取决于值本质,是0则是0,是1则还是1。同时,取余运算的效率远不如与运算
知道这两点后,我们用与运算代替取模运算,h&(length-1)
就相当于 h%length
,而效率大大提升。此时如果传入的是 length 是 2 的次幂,即偶数,则减 1 后变为奇数,二进制位之后都是 1,此时无论传入的 hash 值是奇数还是偶数,最后相与得到的结果是不确定的,这就保证了散列表的均匀性。如果传入的 length 是奇数,则减 1 后变为偶数,此时最后一位永远是 0,此时无论传入的 hash 值是奇数还是偶数,最后的结果永远都是偶数,这样任何 hash 值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间
2.如何进行 hash 运算的
即使散列表很松散,但是因为与 length 的低位相&,所以只能是最后几位,也很容易发生碰撞,这个时候就需要使用 hash 算法
JDK 1.7
// JDK 1.7
inal int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
扰动函数是为了解决hash碰撞的。它会综合hash值高位和低位的特征,并存放在低位,因此在与运算时,相当于高低位一起参与了运算,以减少hash碰撞的概率
在 JDK 1.7 中,使用了 4 次向右移位,就是为了将高位的值与低位的值混合。同时进行了 4 次扰动,目的就是为了增加低位值的随机性,从而使散列分布的更加松散,以此提高性能
JDK 1.8
// JDK 1.8
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
将计算出来的 hash 值向右边移 16 位,正好是 32 位的一半,此时就将高位移到了低位,将高位和低位进行异或运算,增加了低位的随机性,同时也保持了高位的特征
参考
https://blog.youkuaiyun.com/fan2012huan/article/details/51097331
https://www.javazhiyin.com/188.html#m
https://blog.youkuaiyun.com/sidihuo/article/details/78489820 https://blog.youkuaiyun.com/u011240877/article/details/53351188#6hashmap-的获取方法-get
http://ibat.xyz/2017/02/16/浅聊HashMap中的hash算法/
http://www.importnew.com/7099.html