为什么hash%length 和hashlength-1 的值是一样的(当length=2的幂次方)
当 length
是 2 的幂次方时,hash % length
和 hash & (length - 1)
确实等价,这是计算机科学中一个巧妙的位运算优化。下面通过 4 个关键点彻底解释这个原理:
1. 数学本质(核心原理)
当 length = 2ⁿ
时:
-
length - 1
的二进制形式是 全1的比特位
例如:length = 16 (2⁴)
→length - 1 = 15
→ 二进制1111
-
hash & (length - 1)
相当于 保留hash的低n位,其他位归零
这本质上就是hash % length
的余数
2. 实例演示
假设 length = 8
(2³):
length = 8 = 00001000
length - 1 = 7 = 00000111 // 低3位全1
hash = 21 → 二进制 00010101
21 % 8 = 5
21 & 7 = 00010101
& 00000111
---------
00000101 = 5 // 结果相同
3. 为什么等价?
-
取模运算:
hash % length
求的是hash
除以length
的余数 -
位与运算:当
length
是2的幂次方时,length - 1
的二进制形式是00...011...1
(低位全1)
hash & (length - 1)
相当于:-
保留
hash
的低log₂(length)
位 -
丢弃高位(正好是
length
的整数倍部分)
-
4. 优势对比
运算 | 性能 | 适用场景 | 注意事项 |
---|---|---|---|
hash % length | 较慢 | 任意length | 负数需额外处理(Math.abs ) |
hash & (length-1) | 极快 | length必须为2的幂次方 | 自动处理负数(直接截取低位) |
关键结论
-
等价条件:仅当
length
是 2 的幂次方时成立 -
性能差异:位运算比取模快 5-10 倍(CPU指令级优化)
-
HashMap的设计:
JDK 中HashMap
强制容量为2的幂次方(通过tableSizeFor()
方法保证),就是为了使用这个优化技巧
为什么HashMap要用这个技巧?
-
极致性能:位运算是CPU原生支持的最快操作之一
-
避免负数问题:
&
运算天然处理负数的hash值(而%
需要额外处理) -
与扩容机制完美配合:
扩容时只需newIndex = oldIndex | newCapacity
(不需要重新计算hash)
理解这个原理,就掌握了HashMap性能优化的第一个关键设计!