Java中HashMap底层为什么用2的倍数来扩容

最近在看面试题时看到了这个问题

一、前置知识:HashMap 的存储结构

HashMap 中,数据是以键值对(key-value)的形式存在底层数组(桶)中的。我们通过键的 hashCode() 方法得到一个哈希值,然后定位到具体的数组下标,再将数据放入该位置的链表或红黑树中。
详细信息可以看深入理解HashMap底层结构-CSDN博客

这一步中就涉及到:如何将哈希值转为数组下标。

二、下标计算:与操作取代模运算

 通常我们将哈希值转为数组下标,会使用下面这两种方式之一:

✅常规方式(低效):

index = hash % array.length;

模运算 % 的性能相对较低,尤其是在高并发场景中并不理想。

✅ HashMap 使用的方式(高效):

index = (array.length - 1) & hash;

这里的 & 是位运算,比 % 要快得多。但 前提条件是 array.length 必须是 2 的幂次方,这样 length - 1 的二进制刚好全是 1,才可以保留哈希值的低位用于下标定位。

2.1为什么当除数是 2 的幂时,% 运算可以用 & 替代来提升性能

这是一个非常经典的性能优化技巧,广泛应用于 Java 的集合类(如 HashMapConcurrentHashMap)中。我们来深入理解 为什么当除数是 2 的幂时,可以用 & 替代 % 来提升性能。

前提:我们要做什么?

我们想计算一个整数 n 对某个数取模的结果:

int index = n % m;

其中,m 是 2 的幂次,比如:2, 4, 8, 16, 32, ...

核心原理

m = 2^k(即 m 是 2 的幂),那么有如下等价关系:

n % m == n & (m - 1)

 示例说明:

假设:

n = 10(二进制为 1010

m = 8(即 2³)

则:

m - 1 = 7(二进制为 0111

n & (m - 1) 就是取 n 的最低 3 位(因为 0111 只保留低 3 位)

所以:

   1010
&  0111
-------
   0010  → 十进制是 2

而 10 % 8 = 2

两者结果一致

为什么可以这样替换?

因为:

  • 任何整数 n 都可以表示为:
    n = a * m + b,其中 0 ≤ b < m
    所以 n % m = b

  • m = 2^k 时,b 就是 n 的最低 k 位的值

  • n & (m - 1) 正好就是提取这 k 位的值

因此:

n % (2^k) == n & (2^k - 1)

三、扩容机制更高效 

在 Java 中,当 HashMap 的元素数量超过「负载因子 * 当前容量」时,就会触发**扩容(resize)**操作。

扩容的过程

 在 JDK8 中,扩容不是简单地把所有节点重新计算 hash 分布,而是:

  • 数组容量变为原来的两倍(oldCap * 2

  • 元素要么留在原位,要么移动到 原位 + oldCap

判断方式

if ((e.hash & oldCap) == 0)
    // 留在原位置
else
    // 移动到原位置 + oldCap

为什么可以这样做?

因为数组容量是 2 的幂oldCap 一定是某个二进制位为 1 的数。

例如:oldCap = 16 (10000),那么 (e.hash & oldCap) 只会判断 hash 的第 5 位是 0 还是 1:

  • 若是 0 → 不变

  • 若是 1 → 加上 16

这样做的优势: 

优势 描述
高性能 无需重新计算哈希,只需判断某一位即可
低成本 不需要 hash % newLength 这种高开销操作
更优分布 原有哈希值的高低位信息被保留,更利于数据均匀分布

举个实际例子

假设你有一个哈希值 h 10101100 和 n 10010000  ,旧容量 oldCap = 16 (10000)

h & oldCap = 10101100 & 10000 = 0 => 不动  
n & oldCap = 10010000 & 10000 = 10000 => 移动到 index + 16

你只要判断第 5 位是不是 1,就知道它该不该移动。

四、避免哈希冲突,提升性能

4.1 什么是哈希冲突?

当多个键通过哈希函数映射到了数组中的同一个槽(bucket)时,就发生了哈希冲突。冲突越多:

  • 查询效率降低

  • 可能退化成链表甚至红黑树

  • 影响性能上限

4.2为什么使用 2 的幂次可以减少冲突?

来看 HashMap 的核心定位语句:

 

index = (n - 1) & hash

如果 n 是 2 的幂,例如 n = 16,那么 n - 1 = 15 = 00001111。这个操作就能保留哈希值的低 4 位:

假设 hash = 10101100
index = hash & 00001111 = 1100 = 12

这意味着每一位的变化都能影响最终下标,提升了散列效果。

如果不是 2 的幂会怎么样?

例如 n = 10

  • n - 1 = 9 = 00001001

  • index = hash & 00001001 → 只有两位参与了索引定位

  • 很容易发生冲突:多个 hash 虽然不同,但结果可能一样

好的哈希散列特点:

  • 每一位的变化都会影响结果(避免聚集)

  • 越多位参与下标计算,越容易分散

使用 2 的幂时,n - 1 会是二进制的全 1,可以让低位 hash 全部参与运算。

4.3总结对比

比较项 2 的幂 非 2 的幂
散列分布 更均匀 容易聚集冲突
下标计算 快,位运算 慢,需 %
扩容迁移 简单,仅判断一位 复杂,需重新定位
性能影响 高效 查找慢,冲突多

五、一句话总结

“使用 2 的幂次,是为了更快、更简洁地通过位运算定位数组下标,并在扩容时无需重新 hash。”

这就是 HashMap 中的工程之美,简洁而不简单!


如有疑问或觉得有帮助,欢迎点赞、收藏并留言讨论!

你可能感兴趣的:(java,map,hashmap,java,开发语言,hashmap,哈希算法,map,数据结构,算法)