欢迎光临
我们一直在努力

深入解析Java:HashMap扩容机制全过程深度剖析

深入解析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 可视化扩容流程图

    #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 迁移规则

  • 单个节点:直接计算新下标,放入新数组;
  • 链表节点:拆分为高位链表和低位链表,分别放入新数组对应位置;
  • 红黑树节点:拆分迁移,若节点数小于6会退化为链表。

  • 五、源码解析: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次方?

  • 用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🌺点点关注,收藏不迷路🌺

    ⬆ ⬆ 顶部 ⬆ ⬆

    赞(0)
    未经允许不得转载:171主机测评 » 深入解析Java:HashMap扩容机制全过程深度剖析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址