HashMap的hash方法是如何实现的
在深入理解 HashMap
的整体设计之前,先看看它是如何将任意对象的 hashCode()
值,转换成数组索引可用的“桶下标”——也就是 HashMap
中的 hash(Object key)
方法。
/*** Computes key.hashCode() and spreads (XORs) higher bits of hash* to lower. Because the table uses power-of-two length, sets of* hashes that vary only in bits above the current mask will* always collide. (…若只用低位,则高位变化的 hash 会落到同一桶)*/
static final int hash(Object key) {int h;// key.hashCode() 可能是 int 全 32 位,(h = key.hashCode()) 先取其原始值// 然后将 h 右移 16 位,并与原 h 做异或,// 将高 16 位的信息“扩散”到低 16 位,增强低位的随机性return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
一、为什么要对 hashCode()
做“扰动”?
-
hashCode()
分布不一定理想-
不同类、不同实现的
hashCode()
分布可能偏向高位或低位集中。 -
如果直接用
hashCode()
的低几位作为桶下标,而这些位又缺乏随机性,就会导致大量键落到同一桶上,触发冲突。
-
-
HashMap
桶数组长度总是 2 的幂-
在计算数组下标时,
HashMap
用的是(n - 1) & hash
,相当于取hash
的低log₂n
位。 -
举例:当数组长度
n = 16
时,只取hash
的低 4 位。 -
如果原始
hashCode()
的低 4 位一致,无论高位如何不同,都将冲突落到同一个桶。
-
为了避免以上问题,JDK 将 hashCode()
的高 16 位通过“右移并异或”的方式,混合进低 16 位。这样,即使原始 hashCode()
只在高位有所区别,也能反映到低位,降低冲突概率。
二、hash()
方法详解
static final int hash(Object key) {int h;// 如果 key 为 null,则直接返回 0(所有 null 键同样映射到桶 0)// 否则,先取 key.hashCode(),赋值给局部变量 h// 再计算 h ^ (h >>> 16)return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
-
h = key.hashCode()
调用对象自身的hashCode()
方法,获得一个 32 位的初始哈希值。 -
h >>> 16
无符号右移 16 位,将高 16 位移到低 16 位的位置,并补 0。 -
h ^ (h >>> 16)
把原始的h
与右移后的h
做位异或(XOR),混合了高位信息到低位,也保留了低位原有的信息。
三、示例:高位差异如何影响低位
int[] codes = {// 假设 key1.hashCode() = 0x123400010x12340001,// key2.hashCode() = 0x12350001,高位只差 0x000100000x12350001
};for (int hc : codes) {int mixed = hc ^ (hc >>> 16);System.out.printf("原始: 0x%08X, 混合后: 0x%08X\n", hc, mixed);
}
运行结果大致为:
原始: 0x12340001, 混合后: 0x12350001
原始: 0x12350001, 混合后: 0x12360001
可以看到,即使原始哈希只在第 17 位(0x00010000)有差异,经过扰动后,低 16 位也产生了不同,从而在取桶下标时会分散到不同的桶里。
四、与 JDK 7 的对比
-
JDK 7:在
HashMap
实现中,也做了扰动(supplementalHash()
),但代码更复杂,且不同版本略有差异; -
JDK 8:简化为上述的一行关键逻辑,更高效、更易理解。
五、为什么不扰动更多轮?
-
性能权衡:每次扰动都要额外几条位运算指令,
hash()
是高频调用,必需尽量精简; -
足够效果:一次 16 位混合已足以将高 16 位信息带入,极大改善低位分布;
-
简单易维护:单次异或右移既高效又易于理解。
六、在实际使用中的影响
-
减少哈希冲突
-
对于多数自定义
hashCode()
实现,都能更均匀地分布到桶中,保持接近 O(1) 性能。
-
-
抗 DoS 攻击
-
即便攻击者构造一批只在高位或低位相同的 key,仍会被扰动分散到不同桶,降低碰撞风险。
-
-
兼容性
-
对已有的
hashCode()
不做破坏性更改,只是在HashMap
侧做一层灰盒优化,无需用户改动。
-
七、完整代码 & 注释
public class HashMap<K,V> {// ... 省略其他成员 …/** 阻止高碰撞:混合高 16 位至低 16 位 */static final int hash(Object key) {int h;// null 键恒定映射到桶 0if (key == null) {return 0;}// 调用 key.hashCode(),混合高 16 位信息,再返回h = key.hashCode();return h ^ (h >>> 16);}// 计算桶下标:取模 table.lengthstatic int indexFor(int hash, int length) {// length 必为 2^nreturn hash & (length - 1);}// put 操作示意public V put(K key, V value) {int hash = hash(key);int idx = indexFor(hash, table.length);// 在 table[idx] 链表或树中,查找或插入节点…}
}
八、总结
-
HashMap.hash()
方法通过一次右移异或,将hashCode()
的高位信息“扩散”到低位,避免了电商、社交等高并发场景中因hashCode()
分布不均导致的桶级拥堵; -
简洁高效的实现,兼顾性能与抗攻击性,是 Java 8 中
HashMap
能在绝大多数场景下保持稳定 O(1) 操作的重要一环。