HashMap 扩容机制

在 Java 中,HashMap 是最常用的集合类之一,它的底层实现涉及数组、链表和红黑树。理解它的扩容机制,对于写出高效、安全的代码至关重要。本文将从触发条件、迁移流程、版本差异等方面,全面解析 HashMap 的扩容过程。

一、扩容触发条件

HashMap 的扩容主要发生在两种情况下:

1. 元素个数超过阈值

  • 阈值 = 当前数组容量 × 负载因子(默认 0.75)

  • 当 size > 数组容量 × 0.75 时,触发扩容

2. 链表树化前的容量检查(JDK 1.8)

  • 当某个链表的长度超过 8,但数组长度小于 64 时,不会树化,而是先扩容

  • 这是为了避免因数组过小而导致频繁的树化操作

二、扩容流程概述

1. 创建新数组

  • 新数组容量 = 旧数组容量 × 2

2. 元素迁移

JDK 1.8 对迁移过程做了优化,不再像 1.7 那样逐个重新计算下标,而是通过位运算高效拆分。

关键公式
  • 旧下标:index = hash & (oldCap - 1)

  • 新下标判断:

    • 如果 hash & oldCap == 0,则新下标 = 旧下标

    • 如果 hash & oldCap == 1,则新下标 = 旧下标 + 旧容量

三、迁移四部曲(JDK 1.8)

第一步:跳过空桶

  • 遍历旧数组,如果当前位置为 null,则直接跳过

第二步:单个节点

  • 直接计算新下标并放入新数组

第三步:链表拆分

  • 根据 hash & oldCap 将链表拆分为两条:

    • low 链表:新下标 = 旧下标

    • high 链表:新下标 = 旧下标 + 旧容量

  • 拆分后直接放入新数组,避免逐个插入

第四步:红黑树拆分

  • 同样根据 hash & oldCap 拆分为两棵树

  • 如果某棵树节点数 ≤ 6,则退化为链表

  • 否则继续保持红黑树结构

四、JDK 1.7 与 1.8 扩容对比

对比项 JDK 1.7 JDK 1.8
插入方式 头插法(扩容后链表倒置) 尾插法(保持原顺序)
迁移方式 重新计算每个下标,逐个插入 按高低位拆分,整体迁移
并发安全 扩容时可能出现环形链表 避免了环形链表问题
红黑树支持 无 支持,扩容时可能退化为链表
效率 较低 更高,尤其在大数据量下

五、总结

HashMap 的扩容机制在 JDK 1.8 中得到了显著优化,不仅引入了红黑树来提升查找效率,还通过位运算优化了元素迁移过程,避免了链表倒置和环形链表的并发问题。理解这些底层细节,有助于我们在实际开发中更好地使用 HashMap,避免踩坑。

相关推荐
希和202710 小时前
考研英语阅读词汇笔记|城市野径、抄近道步道(非正式小路)
笔记·考研
一个人旅程~13 小时前
关于imac27inch因改短用户名导致混合硬盘和系统崩溃的问题分析与故障解决
经验分享·笔记·macos·新浪微博
心祭丫丫13 小时前
【每天一个 Python 小案例17】Python 实现通讯录
开发语言·人工智能·笔记·python·学习
派大猩猩_15 小时前
Obsidian 笔记怎么同步多端:Nutstore Sync 大笔记库同步测评
笔记·obsidian·obsidian插件·obsidian同步
叼烟扛炮20 小时前
Linux 学习笔记(五十):网络层——IP 协议、子网划分与 NAT 魔法
linux·笔记·学习
叼烟扛炮20 小时前
Linux 学习笔记(六十):单线程的逆袭——epoll 高并发服务器实战
linux·服务器·网络·笔记·学习·算法
Rabitebla21 小时前
【Linux操作系统】进程篇(十一):进程程序替换——从底层原理到 exec 函数族
linux·服务器·c++·笔记·学习·算法
叼烟扛炮1 天前
[LangChain] 学习笔记(03):打破失忆症——对话机制与上下文管理
java·前端·javascript·笔记·学习·langchain
景煜拾年1 天前
InnoAI SQL 助手
运维·数据库·笔记·sql
旖旎夜光1 天前
【C++ 基础入门】类和对象(下):初始化列表、static 成员、友元与内部类
c++·笔记·学习