为什么 HashMap 容量 capacity 大小是 2 的 n 次幂?
为什么使用 e.hash & (capacity - 1) 位运算作取模公式?
为什么扩容时使用 e.hash & oldCap 来计算扩容后的数组索引?
本文通过推导 HashMap 中的取模和扩容公式以回答上述问题。
文章目录
1. 按位与(&)运算的理解
位运算的运算规则如下:
两个二进制的数按位与,如 A & B,当 B 中某一位为 1,则保留 A 上对应位上的数。
假设 B = 1000(二进制),第四位为 1。
当 A = 1001(二进制),A & B 取得第四位为 1,得到 A & B = 1000(二进制);
当 A = 0110(二进制),A & B 取得第四位为 0,得到 A & B = 0000(二进制);
理解了按位与的这一层含义之后,再来看 HashMap 中的取模和扩容算法。
2. 取模运算
HashMap 的取模公式为 e.hash & (capacity - 1) 。
这里 capacity 是 HashMap 数组结构的大小,约定为 2 的 n 次幂,记为 capacity = 2n。
对于节点 e,它的哈希值用 e.hash 表示。
正常来说,取模公式为 e.hash % capacity,为什么 HashMap 中可以用位运算来替代呢?
2.1 当 e.hash 为正数
从二进制角度来看,e.hash / capacity = e.hash / 2n = e.hash >> n,即把 e.hash 右移 n 位,此时得到了 e.hash / 2n 的商。
而被移掉的部分(低 n 位),则是 e.hash % 2n,也就是余数。
如何取得 e.hash 的低 n 位呢?
已知 2n 的二进制形式为 1 后面跟着 n 个 0,则 2n - 1 的二进制形式为 n 个 1。
如 8 = 23,其二进制形式为 1000,7 = 23 - 1,其二进制形式为 111。
根据对按位与(&)操作的理解,e.hash & (2n - 1) 就是取得 e.hash 的低 n 位,同样是余数。
因此我们可以推导出 e.hash & (capacity - 1) = e.hash % capacity。
验证: