hashmap的hash()方法核心是将key的hashcode高16位无符号右移后与低16位异或,使高位信息参与索引计算,减少哈希冲突;源码为(h = key.hashcode()) ^ (h >>> 16),异或运算确保变化敏感、散列均匀。

Java 中 HashMap 的 hash() 方法(也叫“扰动函数”)核心就做了一件事:把 key 的原始 hashCode 高 16 位“混入”低 16 位,再通过异或保留双方变化特征。它不是为了加密或隐藏,而是为了让低位索引计算时也能反映高位差异,从而减少哈希冲突。
具体做了什么位运算
源码实现非常简洁:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这行代码包含两个关键操作:
-
无符号右移 16 位(
h >>> 16):把原始 32 位 int 的高 16 位移到低 16 位位置,高位补 0。例如:
原始h = 0x1234ABCD(二进制高16位是0001001000110100),
右移后变成0x00001234(即00000000000000000001001000110100)。 -
与原值异或(
^):将h和h >>> 16按位异或。相同为 0,不同为 1,不进位、不丢信息。结果的低 16 位是“原低16位 ⊕ 原高16位”,高 16 位基本保留原高16位(但受异或影响略有变化)。
为什么偏偏是右移 16 位
因为 int 是 32 位,16 是它的一半。这个设计不是随意选的,而是兼顾了效率和效果:
- 数组长度始终是 2 的幂(如 16、32、64…),所以真正参与下标计算的只有 hash 值的低若干位(比如长度 16 → 只用低 4 位;长度 1024 → 用低 10 位)。
- 如果直接用原始 hashCode,而它的高 16 位经常变化(比如对象内存地址、时间戳等生成的 hash),但低几位几乎不变,那所有这些 key 就会落到同一个桶里——高位变了,索引却没变,冲突激增。
- 右移 16 位后异或,等于把高 16 位“折叠”进低 16 位。这样即使原始 hash 仅高位不同,扰动后的低 16 位也会明显不同,最终算出的数组下标更分散。
- 选 16 而非 8 或 24,是因为它能最大程度覆盖低位区域,又避免过度扰动导致规律性反而下降;实测表明 16 位在各类数据分布下冲突率最低。
为什么不选 & 或 | 运算
异或(^)是唯一适合“混合+敏感响应”的位运算:
-
&(与):结果偏向 0。只要某一位是 0,结果就是 0 —— 高位变化可能完全被掩盖。 -
|(或):结果偏向 1。只要某一位是 1,结果就是 1 —— 容易让不同 key 算出相同低 16 位。 -
^(异或):0⊕0=0,1⊕1=0,0⊕1=1,1⊕0=1。任意一位变化,结果位必然翻转。高位或低位哪怕只差 1bit,扰动后低 16 位大概率整体不同,对变化最敏感,散列质量最高。
扰动后怎么定位数组下标
扰动只是第一步,真正决定存到哪的是:
int index = (table.length - 1) & hash;
因为 table.length 总是 2 的幂,所以 length - 1 的二进制全是 1(如 16−1=15 → 1111)。& 运算等价于取 hash 的低 log₂(length) 位,比 % 快得多。而扰动确保了这几位不只是原始 hash 的“死板低位”,而是融合了高位信息的“活”低位。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











