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 分别是 2^10、2^20、2^30,那么寻址结果 index 就会一样而发生冲突,所以哈希表就不均匀分布了。
为了减少这种冲突,HashMap 中让 hashCode 的高位也参与了寻址计算(进行扰动),即把 hashCode 高 16 位与 hashCode 进行异或算出 hash,然后根据 hash 来做寻址。