深入解析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个核心关键字(必须牢记)
- 容量(capacity):底层数组的长度,默认16,必须是2的n次方;
- 加载因子(loadFactor):默认0.75,是扩容的判断比例;
- 阈值(threshold):扩容临界值,计算公式:阈值 = 容量 × 加载因子。
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时,未指定容量,首次
put()元素会触发默认扩容(容量变为16); - 批量添加大量元素时,一次性达到阈值会触发扩容;
- JDK1.8中,链表转红黑树时,若数组容量小于64,会优先扩容而非树化。
三、HashMap扩容完整执行流程(JDK1.8)
3.1 扩容核心步骤(7步)
- 计算新容量:新容量 = 旧容量 × 2(翻倍扩容);
- 计算新阈值:新阈值 = 新容量 × 加载因子;
- 创建新的数组:长度为新容量;
- 遍历旧数组的每一个哈希桶;
- 迁移数据:重新计算元素在新数组的位置,转移节点;
- 旧数组所有数据迁移完成;
- 将HashMap的数组引用指向新数组,扩容完成。
3.2 可视化扩容流程图
四、扩容核心细节:数据如何迁移?
4.1 JDK1.8优化亮点
JDK1.8中,元素位置计算公式:下标 = hash & (新容量-1)
因为容量是2倍扩容,所以元素在新数组中只有两种位置:
- 原下标位置不变;
- 原下标 + 旧容量位置。
无需重新计算hash,只需判断hash的高位值,效率大幅提升!
4.2 迁移规则
- 单个节点:直接计算新下标,放入新数组;
- 链表节点:拆分为高位链表和低位链表,分别放入新数组对应位置;
- 红黑树节点:拆分迁移,若节点数小于6会退化为链表。
五、源码解析:HashMap扩容核心代码
finalNode<K,V>[]resize(){Node<K,V>[]oldTab=table;// 旧数组intoldCap=(oldTab==null)?0:oldTab.length;// 旧容量intoldThr=threshold;// 旧阈值intnewCap,newThr=0;// 1. 计算新容量和新阈值if(oldCap>0){newCap=oldCap<<1;// 容量翻倍:×2newThr=oldThr<<1;// 阈值翻倍}// 2. 创建新数组Node<K,V>[]newTab=(Node<K,V>[])newNode[newCap];table=newTab;// 3. 数据迁移if(oldTab!=null){for(intj=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;// 红黑树迁移elseif(einstanceofTreeNode)((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;elseloTail.next=e;loTail=e;}// 高位链表:存原下标+旧容量else{if(hiTail==null)hiHead=e;elsehiTail.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;}}}}}returnnewTab;}六、扩容高频面试题答案(背会直接用)
6.1 问:HashMap什么时候扩容?
答:
向HashMap添加元素后,若元素数量size ≥ 阈值(容量×加载因子),就会触发扩容。
6.2 问:HashMap扩容后容量是多少?
答:
扩容为原容量的2倍,且始终保持是2的n次方。
6.3 问:为什么容量必须是2的n次方?
答:
- 用
hash & (容量-1)代替取模运算,效率更高; - 保证元素均匀分布,减少哈希冲突;
- 扩容时能快速计算新下标,优化数据迁移。
6.4 问:JDK1.7和1.8扩容区别?
答:
- JDK1.7:头插法,多线程扩容会形成环形链表死循环;
- JDK1.8:尾插法,拆分高低位链表,解决死循环,效率更高。
七、总结:HashMap扩容核心知识点
- 核心定义:扩容是创建更大数组,迁移数据的过程;
- 触发条件:size ≥ 容量×加载因子(默认0.75);
- 容量规则:必须是2的n次方,指定非2的n次方数会自动向上取整;
- 扩容大小:每次扩容为原容量的2倍;
- 数据迁移:JDK1.8拆分高低位链表,效率极高,无死循环。
结束语
HashMap扩容机制是Java集合最核心的知识点,贯穿了数据结构、算法、并发安全等多个维度。理解了扩容流程,不仅能轻松应对面试,更能在开发中合理设置初始容量,提升程序性能。
建议结合流程图和源码反复练习,彻底掌握这个高频考点!
🌺The End🌺点点关注,收藏不迷路🌺 ⬆ ⬆ 顶部 ⬆ ⬆ |