JDK 1.8 中 HashMap 的 hash 算法和寻址算法

HashMap 源码

hash() 方法

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

h = key.hashCode() 表示 h 是 key 对象的 hashCode 返回值;

h >>> 16 是 h 右移 16 位,因为 int 是 4 字节,32 位,所以右移 16 位后变成:左边 16 个 0 + 右边原 h 的高 16 位;

最后把这两个进行异或返回。

putVal() 中寻址部分

tab[i = (n - 1) & hash]

tab 就是 HashMap 里的 table 数组 Node<K,V>[] table

n 是这个数组的长度 length;

hash 就是上面 hash() 方法返回的值;

为什么不直接用 hashCode() % length ?

看完源码会有疑问,为什么不直接用 key 对象的 hashCode 对哈希表长度取模?

寻址为什么不用取模?

对于上面寻址算法,由于计算机对比取模,与运算会更快。所以为了效率,HashMap 中规定了哈希表长度为 2 的 k 次方,而 2^k-1 转为二进制就是 k 个连续的 1,那么 hash & (k 个连续的 1) 返回的就是 hash 的低 k 个位,该计算结果范围刚好就是 0 到 2^k-1,即 0 到 length - 1,跟取模结果一样。

也就是说,哈希表长度 length 为 2 的整次幂时, hash & (length - 1) 的计算结果跟 hash % length 一样,而且效率还更好。

为什么不直接用 hashCode() 而是用它的高 16 位进行异或计算新 hash 值?

int 类型占 32 位,可以表示 2^32 种数(范围:-2^31 到 2^31-1),而哈希表长度一般不大,在 HashMap 中哈希表的初始化长度是 16(HashMap 中的 DEFAULT_INITIAL_CAPACITY),如果直接用 hashCode 来寻址,那么相当于只有低 4 位有效,其他高位不会有影响。这样假如几个 hashCode 分别是 220、2^30,那么寻址结果 index 就会一样而发生冲突,所以哈希表就不均匀分布了。

为了减少这种冲突,HashMap 中让 hashCode 的高位也参与了寻址计算(进行扰动),即把 hashCode 高 16 位与 hashCode 进行异或算出 hash,然后根据 hash 来做寻址。

JDK 源码中 HashMap 的 hash 方法原理是什么?

05-11 22:13