HashMap扩容触发条件是put时size≥threshold,即第13个元素插入时首次扩容;扩容通过(e.hash & oldCap)==0判断位置,0则原位、否则原位+oldCap;非线程安全,多线程扩容会导致死链或数据丢失;扩容为O(n)操作,应预设初始容量避免频繁扩容。

HashMap 扩容触发条件是什么
当 put 新元素时,如果当前 size(已存键值对数量) ≥ threshold(阈值),就会触发扩容。这个阈值初始为 capacity × loadFactor,默认容量是 16,负载因子是 0.75,所以首次扩容发生在第 13 个元素插入后(16 × 0.75 = 12,插入第 13 个时触发)。
注意:不是看数组是否填满,而是看逻辑 size 是否超过 threshold;即使有大量 null 桶,只要 size 达标就扩容。
扩容时如何重新计算桶位置
Java 8 中,扩容后新容量是原容量的 2 倍,而 key 的新下标由 (e.hash & oldCap) == 0 判断决定——这是关键优化点,避免了全部 rehash。
- 如果
e.hash & oldCap == 0,节点留在原下标位置 - 否则,节点移到
原下标 + oldCap位置
这是因为扩容前后 hash 值不变,而新容量是 2 的幂,所以只需多看一位 bit 就能区分迁移方向。这比老版本(JDK 7)全量调用 hash() + 取模快得多。
立即学习“Java免费学习笔记(深入)”;
扩容过程会不会阻塞其他线程
单线程环境下无问题;但 HashMap 本身不是线程安全的,多线程同时触发扩容会引发多种问题:
- 死链(JDK 7 中因头插法+并发导致链表循环)
-
数据丢失(JDK 8 中虽改用尾插,但并发
put仍可能覆盖) -
ConcurrentHashMap才真正支持并发扩容,它通过分段锁 + 协助扩容机制实现
所以生产环境若需线程安全且高吞吐,不要靠 synchronized 包裹 HashMap,应直接选用 ConcurrentHashMap 或明确控制写入时机。
扩容对性能的实际影响有哪些
扩容本质是一次 O(n) 的内存分配 + 元素遍历 + 重散列,虽然 JDK 8 优化了迁移逻辑,但仍是重量级操作:
- 会引发一次完整的 GC 压力(新数组分配、旧对象可能进入老年代)
- 如果初始容量预估过小(如默认 16),频繁扩容会导致 CPU 和内存抖动
- 建议在创建时显式指定初始容量:比如确定要放 1000 个元素,可设为
new HashMap(1024)(1024 是大于 1000/0.75 的最小 2 的幂)
int expectedSize = 1000; int initialCapacity = (int) Math.ceil(expectedSize / 0.75f); initialCapacity = Integer.highestOneBit(initialCapacity); // 确保是 2 的幂 Mapmap = new HashMap<>(initialCapacity);
真正容易被忽略的是:扩容不是“慢”,而是“不可控的突发开销”——它可能在你最不想卡顿的时候发生,比如实时接口的某个请求里突然耗时飙升几十毫秒。











