jdk 8扰动函数为static final int hash(object key) { int h; return (key == null) ? 0 : (h = key.hashcode()) ^ (h >>> 16); },通过高位右移16位后与原值异或,使高位信息融入低位,改善因数组长度为2的幂导致的索引分布不均问题。

HashMap的扰动函数是一次轻量级位运算,作用是把键的原始哈希值“搅一搅”,让高位信息参与低位索引计算,从而缓解因数组长度为2的幂导致的分布不均问题。
扰动函数长什么样
JDK 8中的实现就一行代码:
static final int hash(Object key) {int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
它对非null键取hashCode(),再将该值右移16位后与原值异或。这个操作不加密、不加盐,只是高效混合高低位。
为什么原始hashCode容易导致聚集
HashMap用(n - 1) & hash算下标,前提是数组长度n是2的幂(如16、32)。此时n−1全是1(比如15→1111),&运算只保留hash的低几位。
- 若多个key的hashCode仅高位不同、低位相同(如0x12345678和0x9abc5678),低4位都是01111000,&15后全得8 → 全挤进table[8]
- 尤其常见于递增ID、短字符串等场景:hashCode就是1,2,3,4…,低位规律极强,映射到16槽表时大量集中在前几个桶
扰动怎么改善分布
异或(^)有“相同为0、不同为1”的特性,能有效融合两段数据:
- 原hash = 0x12345678 → 高16位是0x1234,低16位是0x5678
- h >>> 16 = 0x00001234
- h ^ (h >>> 16) = 0x12345678 ^ 0x00001234 = 0x1234444c
关键变化:原来无关紧要的高16位,现在“渗透”进了低16位。最终参与& (n−1)的低位,既含原始低位特征,又混入了高位差异——哪怕原hashCode低4位一样,扰动后大概率不同,桶分布自然更开。
它不是万能,但很务实
扰动函数不解决语义级碰撞(如"Aa"和"BB"天然hashCode相同),也不替代正确重写hashCode()。但它在不干预用户代码的前提下,对大量低质量哈希码做了兜底优化:
- 计算开销极小:一次右移+一次异或,CPU几纳秒
- 配合2的幂容量和0.75负载因子,能把平均链长从5降到2左右,显著推迟红黑树转化
- 分库分表等场景也沿用该逻辑,验证了其工程有效性











