深入解析Java:HashMap扩容机制全过程深度剖析
-
- 前言
- 一、核心基础:HashMap扩容必备概念
-
- 1.1 什么是HashMap扩容?
- 1.2 3个核心关键字(必须牢记)
- 1.3 关键知识点:容量必须是2的n次方
- 二、HashMap扩容触发时机
-
- 2.1 核心触发条件
- 2.2 特殊触发场景
- 三、HashMap扩容完整执行流程(JDK1.8)
-
- 3.1 扩容核心步骤(7步)
- 3.2 可视化扩容流程图
- 四、扩容核心细节:数据如何迁移?
-
- 4.1 JDK1.8优化亮点
- 4.2 迁移规则
- 五、源码解析:HashMap扩容核心代码
- 六、扩容高频面试题答案(背会直接用)
-
- 6.1 问:HashMap什么时候扩容?
- 6.2 问:HashMap扩容后容量是多少?
- 6.3 问:为什么容量必须是2的n次方?
- 6.4 问:JDK1.7和1.8扩容区别?
- 七、总结:HashMap扩容核心知识点
- 结束语
|
🌺The Begin🌺点点关注,收藏不迷路🌺 ⬇ ⬇ 底部 ⬇ ⬇ |
前言
HashMap是Java开发中最常用的集合,扩容机制是HashMap的核心灵魂,也是面试高频考点。很多开发者只知扩容会重新分配数组,却不懂何时触发扩容、容量如何计算、数据如何迁移、JDK1.8做了哪些优化。
本文从基础概念、触发时机、容量计算、完整流程、源码解析、流程图六大维度,彻底拆解HashMap扩容机制,帮你一次性吃透这个核心知识点!
一、核心基础:HashMap扩容必备概念
1.1 什么是HashMap扩容?
扩容(resize):当HashMap中元素数量达到阈值,无法再存储更多数据时,创建一个新的更大的数组,将原数组数据重新计算位置并迁移到新数组,替换旧数组的过程。
简单理解:小水桶装不下水了,换一个大水桶,再把水倒过去。
1.2 3个核心关键字(必须牢记)
1.3 关键知识点:容量必须是2的n次方
规则:
- 如果指定容量cap本身是2的n次方,最终容量=cap;
- 如果不是,最终容量=大于cap的最小2的n次方数。
示例:
- cap=3 → 容量=4(2²)
- cap=4 → 容量=4(2²)
- cap=5 → 容量=8(2³)
- cap=9 → 容量=16(2⁴)
二、HashMap扩容触发时机
2.1 核心触发条件
执行put()添加元素成功后,判断: 当前元素数量 size ≥ 阈值 threshold 满足条件,立即触发扩容!
2.2 特殊触发场景
三、HashMap扩容完整执行流程(JDK1.8)
3.1 扩容核心步骤(7步)
3.2 可视化扩容流程图
#mermaid-svg-1sDT9w6mksMwIT2r{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1sDT9w6mksMwIT2r .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1sDT9w6mksMwIT2r .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1sDT9w6mksMwIT2r .error-icon{fill:#552222;}#mermaid-svg-1sDT9w6mksMwIT2r .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1sDT9w6mksMwIT2r .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1sDT9w6mksMwIT2r .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1sDT9w6mksMwIT2r .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1sDT9w6mksMwIT2r .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1sDT9w6mksMwIT2r .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1sDT9w6mksMwIT2r .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1sDT9w6mksMwIT2r .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1sDT9w6mksMwIT2r .marker.cross{stroke:#333333;}#mermaid-svg-1sDT9w6mksMwIT2r svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1sDT9w6mksMwIT2r p{margin:0;}#mermaid-svg-1sDT9w6mksMwIT2r .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-1sDT9w6mksMwIT2r .cluster-label text{fill:#333;}#mermaid-svg-1sDT9w6mksMwIT2r .cluster-label span{color:#333;}#mermaid-svg-1sDT9w6mksMwIT2r .cluster-label span p{background-color:transparent;}#mermaid-svg-1sDT9w6mksMwIT2r .label text,#mermaid-svg-1sDT9w6mksMwIT2r span{fill:#333;color:#333;}#mermaid-svg-1sDT9w6mksMwIT2r .node rect,#mermaid-svg-1sDT9w6mksMwIT2r .node circle,#mermaid-svg-1sDT9w6mksMwIT2r .node ellipse,#mermaid-svg-1sDT9w6mksMwIT2r .node polygon,#mermaid-svg-1sDT9w6mksMwIT2r .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1sDT9w6mksMwIT2r .rough-node .label text,#mermaid-svg-1sDT9w6mksMwIT2r .node .label text,#mermaid-svg-1sDT9w6mksMwIT2r .image-shape .label,#mermaid-svg-1sDT9w6mksMwIT2r .icon-shape .label{text-anchor:middle;}#mermaid-svg-1sDT9w6mksMwIT2r .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1sDT9w6mksMwIT2r .rough-node .label,#mermaid-svg-1sDT9w6mksMwIT2r .node .label,#mermaid-svg-1sDT9w6mksMwIT2r .image-shape .label,#mermaid-svg-1sDT9w6mksMwIT2r .icon-shape .label{text-align:center;}#mermaid-svg-1sDT9w6mksMwIT2r .node.clickable{cursor:pointer;}#mermaid-svg-1sDT9w6mksMwIT2r .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1sDT9w6mksMwIT2r .arrowheadPath{fill:#333333;}#mermaid-svg-1sDT9w6mksMwIT2r .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1sDT9w6mksMwIT2r .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1sDT9w6mksMwIT2r .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1sDT9w6mksMwIT2r .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1sDT9w6mksMwIT2r .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1sDT9w6mksMwIT2r .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1sDT9w6mksMwIT2r .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1sDT9w6mksMwIT2r .cluster text{fill:#333;}#mermaid-svg-1sDT9w6mksMwIT2r .cluster span{color:#333;}#mermaid-svg-1sDT9w6mksMwIT2r div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1sDT9w6mksMwIT2r .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1sDT9w6mksMwIT2r rect.text{fill:none;stroke-width:0;}#mermaid-svg-1sDT9w6mksMwIT2r .icon-shape,#mermaid-svg-1sDT9w6mksMwIT2r .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1sDT9w6mksMwIT2r .icon-shape p,#mermaid-svg-1sDT9w6mksMwIT2r .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1sDT9w6mksMwIT2r .icon-shape .label rect,#mermaid-svg-1sDT9w6mksMwIT2r .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1sDT9w6mksMwIT2r .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1sDT9w6mksMwIT2r .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1sDT9w6mksMwIT2r :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
执行put()添加元素成功
判断size ≥ 阈值?
结束流程
开始执行resize()扩容
计算新容量=旧容量×2
计算新阈值=新容量×0.75
创建新的哈希数组
遍历旧数组所有哈希桶
重新计算元素位置,迁移数据
数据迁移完成
HashMap指向新数组
扩容完成
四、扩容核心细节:数据如何迁移?
4.1 JDK1.8优化亮点
JDK1.8中,元素位置计算公式:下标 = hash & (新容量-1) 因为容量是2倍扩容,所以元素在新数组中只有两种位置:
无需重新计算hash,只需判断hash的高位值,效率大幅提升!
4.2 迁移规则
五、源码解析:HashMap扩容核心代码
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; // 旧数组
int oldCap = (oldTab == null) ? 0 : oldTab.length; // 旧容量
int oldThr = threshold; // 旧阈值
int newCap, newThr = 0;
// 1. 计算新容量和新阈值
if (oldCap > 0) {
newCap = oldCap << 1; // 容量翻倍:×2
newThr = oldThr << 1; // 阈值翻倍
}
// 2. 创建新数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 3. 数据迁移
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 单个节点直接迁移
if (e.next == null)
newTab[e.hash & (newCap – 1)] = e;
// 红黑树迁移
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// 链表迁移
else {
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// 低位链表:存原下标
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
// 高位链表:存原下标+旧容量
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
六、扩容高频面试题答案(背会直接用)
6.1 问:HashMap什么时候扩容?
答: 向HashMap添加元素后,若元素数量size ≥ 阈值(容量×加载因子),就会触发扩容。
6.2 问:HashMap扩容后容量是多少?
答: 扩容为原容量的2倍,且始终保持是2的n次方。
6.3 问:为什么容量必须是2的n次方?
答:
6.4 问:JDK1.7和1.8扩容区别?
答:
七、总结:HashMap扩容核心知识点
结束语
HashMap扩容机制是Java集合最核心的知识点,贯穿了数据结构、算法、并发安全等多个维度。理解了扩容流程,不仅能轻松应对面试,更能在开发中合理设置初始容量,提升程序性能。
建议结合流程图和源码反复练习,彻底掌握这个高频考点!

|
🌺The End🌺点点关注,收藏不迷路🌺 ⬆ ⬆ 顶部 ⬆ ⬆ |




