HashMap计算Hash值的核心是先对key的hashCode()进行高位参与的扰动(h ^ (h >>> 16)),再与(n-1)按位与定位桶索引;扰动旨在混合高/低位信息,提升低几位区分度,降低因低位雷同导致的哈希冲突概率。

Java 中 HashMap 计算 Hash 值的核心是:先对键(key)的 hashCode() 做一次**高位参与运算的扰动(也叫二次哈希)**,再用扰动后的值与数组长度减一做按位与(&),得到最终索引位置。这个扰动函数不是为了“消除冲突”,而是让原本只依赖低几位的哈希分布更均匀,从而**降低因哈希值低位相同导致的桶冲突概率**。
为什么需要扰动函数?
Java 中对象默认的 hashCode() 通常由内存地址生成,低位变化可能不敏感;而 HashMap 底层数组长度总是 2 的幂(如 16、32、64…),取索引时用的是 hash & (table.length - 1) —— 这等价于取 hash 的低几位。如果多个 hashCode 低位雷同(比如都是偶数、末尾几位固定),就会全部映射到同一个桶,造成严重哈希碰撞。
扰动函数怎么实现?
在 JDK 8 中,HashMap.hash() 方法就是扰动函数:
// JDK 8 源码简化
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
它把原始哈希值 h 的高 16 位无符号右移后,与低 16 位异或(^)。这样做的效果是:让高 16 位的信息“混合”进低 16 位,使得最终用于取模的低几位能反映整个哈希值的特征,大幅提升低位区分度。
立即学习“Java免费学习笔记(深入)”;
扰动后如何定位桶?
假设当前数组长度为 16(即 table.length = 16),那么 table.length - 1 = 15(二进制 1111)。此时索引计算为:
index = hash(key) & 15
由于 15 只有低 4 位是 1,所以结果只取决于扰动后 hash 的低 4 位。但因为扰动已把高 16 位信息“掺入”低 16 位,这低 4 位就不再只是原始 hashCode 的简单截断,而是更随机、更分散。
例如:
- 原始
hashCode = 0xAAAA0001(低 4 位是0001) - 扰动后
hash = 0xAAAA0001 ^ 0x0000AAAA = 0xAAABAAAA(低 4 位变成1010) - 与
15相与得10,而非原来的1
扰动不能完全避免冲突,但很有效
哈希冲突本质无法彻底避免(鸽巢原理),扰动函数的目标是让冲突**尽可能均匀分布**。配合 HashMap 后续的链表转红黑树(JDK 8+,链表长度 ≥ 8 且数组长度 ≥ 64)、扩容机制(负载因子 0.75),整套设计在实践中能高效应对绝大多数场景。真正影响性能的,往往是键的 hashCode() 实现本身是否合理——比如重写 equals() 时没同步重写 hashCode(),或返回常量,那再强的扰动也无济于事。


















