hashmap在元素数量超过阈值(capacity×loadfactor)时立即触发扩容,如默认容量16、负载因子0.75,阈值为12,插入第13个元素前完成扩容;新容量为旧容量左移1位(翻倍),新阈值同步翻倍。

HashMap 容量达到阈值时,会立即在下一次 put 操作中触发扩容,不是等数组填满才动,而是“防堵”式主动扩容——比如默认构造下,12 个元素就触发,第 13 个元素写入前完成扩容。
扩容触发的准确条件
扩容判断发生在 putVal() 方法末尾:当 size(当前键值对数量)≥ threshold(阈值)时启动 resize()。而阈值 = 当前容量 × 负载因子(默认 0.75)。例如:
- 初始容量 16 → 阈值 = 16 × 0.75 = 12 → 插入第 13 个元素前扩容
- 扩容后容量变为 32 → 新阈值 = 32 × 0.75 = 24
- 若已预设初始容量为 100,实际容量会向上取整为最接近的 2 的幂(即 128),阈值 = 128 × 0.75 = 96
扩容时的新容量和新阈值怎么算
扩容不是简单加一,而是按位左移实现翻倍,并确保始终是 2 的幂:
- 新容量 = 旧容量 MAXIMUM_CAPACITY(2³⁰)
- 新阈值 = 旧阈值
- 若 HashMap 尚未初始化(
table == null),但构造时指定了初始容量,则直接用该值作为新容量,再计算阈值 - 若用的是无参构造器且首次扩容,新容量 = 16,新阈值 = 12
元素如何迁移到新数组
扩容核心是 resize() 中的重哈希与再分配,关键点在于高效定位原位置元素在新数组中的归属:
- 对每个非空桶,遍历其链表或红黑树节点
- 利用
(e.hash & oldCap) == 0判断:结果为 0 表示仍在原下标;为 1 表示下标 = 原下标 + 旧容量 - 这样只需一次遍历、无需重新计算 hash,也避免了 JDK 1.7 的死链问题
- 单节点直接搬;链表拆成高低位两个子链再拼接;红黑树则先拆链再树化(节点数 ≤6 时退化为链表)
为什么不能忽略初始化容量
频繁扩容代价高:每次都要新建数组、遍历全部元素、重哈希、搬运、可能树化/链化。存 1000 个元素,从默认 16 开始会经历约 7 次扩容(16→32→64→128→256→512→1024),带来明显性能抖动。
- 建议根据预估数据量设置初始容量:如预计存 800 个键值对,可设
new HashMap(1024) - 也可用
tableSizeFor(预期数量 / 0.75f)反推最小 2 的幂容量 - 负载因子不建议随意调大(如设为 1),虽省空间但链表变长,查询退化严重
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











