一、前提说明本文讨论的扩容特指HashMap 完成初始化之后因新增元素导致 size 超过阈值而触发的扩容过程不包含第一次创建内部数组的情况。JDK 7 与 JDK 8 在这一时机的处理逻辑存在根本性差异背后的设计思想也大不相同。二、JDK 7先扩容再插入2.1 执行流程JDK 7 在插入新节点前会进行一个双重判断当前元素数量是否已达到扩容阈值size threshold待插入的新节点所在的桶是否已有元素即是否发生哈希冲突。只有两个条件同时满足时才会先执行扩容再将新节点插入扩容后的空数组。如果目标桶为空即使 size 已经达到阈值也不会立即扩容而是直接把新节点放进空桶。// JDK 7 插入逻辑简化示意if (size threshold table[bucketIndex] ! null) {resize(); // 扩容bucketIndex indexFor(hash, table.length); // 重新计算下标}addEntry(...); // 插入新节点2.2 优点避免新节点重复迁移新节点不会先放入旧数组、再马上参与数据迁移减少了不必要的搬运开销。空桶场景下的内存节省当新节点落入空桶时暂不扩容可以在哈希冲突较少时延迟数组分配一定程度上节约内存。2.3 缺点扩容规则不够统一是否触发扩容不仅取决于size和threshold还与目标桶是否为空相关。两个容量相同、元素数量相同的 HashMap可能仅仅因为新节点落入的桶不同一个触发扩容而另一个不扩容。这导致扩容行为不易预测负载因子的语义也被弱化。插入流程复杂化提前扩容后由于数组长度改变需要重新计算新节点的存储位置增加了插入路径的复杂度。三、JDK 8先插入确认新增后再扩容3.1 执行流程JDK 8 将扩容判断后置先完成桶内查找与插入可能是链表追加或红黑树插入若本次插入确实是一个新键非覆盖旧值则size加 1若插入后的size threshold触发扩容。// JDK 8 插入逻辑简化示意NodeK,V e ...; // 在桶中完成查找/插入if (e null) { // 新增键size;if (size threshold)resize();}afterNodeInsertion(evict); // 回调3.2 优点1. 扩容条件更清晰、统一是否扩容完全由size threshold决定不再依赖新节点是否落入空桶。负载因子成为真正意义上的“容量饱和度”指标语义明确行为可预测。2. 仅新增键时触发扩容如果调用put的 key 已存在仅仅覆盖旧值size保持不变自然也不会引发扩容避免了无意义的数组重建。3. 与红黑树机制无缝配合JDK 8 引入了“链表转红黑树”的优化。采用“先完成桶内部操作再统一处理树化、size 更新和扩容判断”的流程代码结构更加一致逻辑内聚便于维护。4. 扩容迁移代价降低使后置扩容可行JDK 8 的迁移算法做了关键优化因为数组容量按 2 倍扩展节点在新数组中的下标只有两种可能——保持原下标或原下标 旧容量。只需判断节点哈希值中与oldCap对应的那一位即可将原链表拆分为“高位链”和“低位链”无需重新计算完整哈希下标。因此即使新节点刚刚插入旧数组就立即参与一次迁移额外成本也远低于 JDK 7这使得“先插入后扩容”的设计在性能上完全可接受。// JDK 8 扩容拆分示意NodeK,V loHead null, loTail null;NodeK,V hiHead null, hiTail null;for (NodeK,V e oldTab[j]; e ! null; e e.next) {if ((e.hash oldCap) 0) {// 保留在原下标} else {// 移动到 原下标 oldCap}}3.3 缺点临界场景下的重复迁移新节点可能刚刚放入旧数组就因为扩容被再次迁移发生一次“无用搬运”。不再因空桶而延迟扩容JDK 8 丢弃了“目标桶为空则不扩容”的优化只要 size 超过阈值就会立即扩容可能比 JDK 7 更早分配更大的数组在内存敏感的场景下略显激进。四、设计取舍总结JDK 7 与 JDK 8 在扩容时机上的差异本质上是一组设计取舍JDK 7 偏向局部优化尽量避免新节点的重复迁移同时在冲突较少时通过延迟扩容来节省内存。代价是扩容条件复杂化、行为不一致代码逻辑耦合度高。JDK 8 追求全局统一与可维护性用一次可能的额外迁移换取了扩容规则的纯粹与统一负载因子语义的严格保证与红黑树机制的和谐共生更清晰的代码结构与更可预测的性能表现因此不能简单地说 JDK 8 的“先插入再扩容”就一定更快。更准确的理解是正是因为 JDK 8 优化了迁移算法2 倍扩容下标二选一并引入了红黑树等新结构“先插入、确认 size 增加后再统一扩容”才成为更合适的设计选择。这也是 JDK 在不断演进中根据内部机制的变化对同一问题给出的不同最优解。五、对比一览维度JDK 7JDK 8扩容时机插入前size≥阈值且桶非空插入后新增键且 size阈值扩容规则依赖哈希冲突情况不够统一纯粹依赖 size 与阈值统一清晰新节点迁移避免重复迁移可能刚插入就迁移一次空桶行为可延迟扩容节省内存不再延迟size 超阈值必扩容迁移算法重新计算所有节点下标只需拆分高位/低位链表代码复杂度插入路径分支多逻辑统一树化与扩容分离配合机制纯链表链表 红黑树理解这些差异有助于我们在不同的应用场景中更合理地评估 HashMap 的性能表现同时也体现了 JDK 设计团队在性能、语义与可维护性之间的精妙平衡。