前言:
在 Java 的 hashCode()
计算中,经常会看到 31 * x
这样的乘法运算。JVM 会将其优化为更高效的位运算形式 (x << 5) - x
,以提高计算速度。下面详细解释这一优化原理:
1. 为什么选择 31?
- 31 是一个质数,能减少哈希冲突(质数在取模运算时分布更均匀)。
- 31 的二进制形式是
11111
**(即2⁵ - 1 = 31
),这使得它特别适合用移位和减法优化。
2. 数学等价性
31 * x
可以拆解为:
java
31 * x = (32 - 1) * x = 32 * x - x
而 32 * x
在二进制中相当于 x
左移 5 位(因为 32 = 2⁵
):
java
32 * x = x << 5
因此:
java
31 * x = (x << 5) - x
3. 为什么位运算更快?
- 乘法运算(
*
):在 CPU 上可能需要多个时钟周期(通常为3~4个CPU周期),尤其是早期处理器。 - 移位(
<<
)和减法(-
):是单周期指令(各需要一个周期,组合后仅需要2个周期),执行速度更快。
JVM(特别是 HotSpot)会在运行时自动将 31 * x
替换为 (x << 5) - x
,从而提升 hashCode()
的计算效率。
4. 验证优化
可以通过查看 JVM 生成的汇编代码(使用 -XX:+PrintAssembly
)来确认这一优化。例如:
java
public int hash31(int x) {
return 31 * x;
}
JIT 编译器会将其优化为:
java
mov eax, x ; 加载 x 到寄存器
shl eax, 5 ; x << 5
sub eax, x ; eax = eax - x
5. 其他类似优化
JVM 对某些固定乘数(如 3, 7, 15, 31, 63 等 2ⁿ -1
形式的数)会采用类似的优化:
7 * x
→(x << 3) - x
15 * x
→(x << 4) - x
63 * x
→(x << 6) - x
但 31 是最常用的,因为它在哈希计算中平衡了性能和冲突率。
总结
- 优化原理 :
31 * x = (x << 5) - x
,利用二进制特性转换为高效位运算。 - 性能提升:移位和减法比直接乘法更快,JVM 自动应用此优化。
- 适用场景 :适用于
hashCode()
计算、字符串哈希等需要快速乘法的场景。
这种优化是 JVM 对常见模式的智能编译策略之一,开发者无需手动改写,直接使用 31 * x
即可获得最佳性能。