JDK8 HashMap红黑树退化为链表的机制解析
目录
1、数据结构:
2、Fail-Fast机制
2.1、核心作用
2.2、实现原理
2.3、触发场景
2.4、实现细节
2.5、对比
2.6、注意事项
3、核心结论
4、转化安全机制
4.1. 触发场景
4.2. 转换过程
4.3. 并发安全机制
5、设计原因
5.1. 性能权衡
5.2. 空间局部性
5.3. 实际测试数据
6、常见误区
7、实战建议
前言
在之前对jdk8的hashmap的结构进行了深入的学习,可参考:HashMap的扩容机制-CSDN博客
1、数据结构:
jdk8及之后,由hashmap由数组+链表(红黑树组成)。
如下图所示:
桶数组是用来存储数据元素,链表是用来解决冲突,红黑树是为了提高查询的效率。
数据元素通过映射关系,也就是散列函数,映射到桶数组对应索引的位置。
如下图所示:
如果发生冲突,从冲突的位置拉一个链表,插入冲突的元素。
如果链表长度>8&数组大小>=64,链表转为红黑树。
如果红黑树节点个数<6 ,转为链表。
2、Fail-Fast机制
Fail-Fast(快速失败)是Java集合框架中一种重要的并发修改检测机制,在HashMap中主要用于防止在迭代过程中集合被意外修改而导致数据不一致的问题。
2.1、核心作用
Fail-Fast机制就像集合的"安全警报系统":
-
实时监控:检测迭代期间的意外修改
-
快速响应:立即抛出
ConcurrentModificationException
-
预防损害:避免产生不可预知的错误结果
2.2、实现原理
1. 关键变量
// HashMap中的修改计数器
transient int modCount;// 迭代器中保存的计数器快照
int expectedModCount;
2. 工作流程
2.3、触发场景
1. 迭代时修改集合
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {String key = it.next();map.put("C", 3); // 这里会触发fail-fast
}
2. 多线程并发修改
Map<Integer, String> map = new HashMap<>();
map.put(1, "One");new Thread(() -> {map.put(2, "Two"); // 可能触发主线程迭代时fail-fast
}).start();for (Integer key : map.keySet()) { // 可能抛出异常System.out.println(key);
}
2.4、实现细节
1. 修改计数更新点
// HashMap中的修改操作都会增加modCount
public V put(K key, V value) {// ...++modCount;// ...
}public V remove(Object key) {// ...++modCount;// ...
}public void clear() {// ...++modCount;// ...
}
2. 迭代器检查点
final class KeyIterator extends HashIterator implements Iterator<K> {public final K next() {if (modCount != expectedModCount)throw new ConcurrentModificationException();// ...}
}
2.5、对比
2.6、注意事项
1.正确删除元素:
// 错误方式(触发fail-fast)
for (String key : map.keySet()) {if (key.equals("remove")) {map.remove(key); // 直接修改原集合}
}// 正确方式(使用迭代器的remove)
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {if (it.next().equals("remove")) {it.remove(); // 不会增加modCount}
}
2.多线程解决方案:
-
使用
ConcurrentHashMap
替代 -
或者使用显式同步:
synchronized(map) {for (String key : map.keySet()) {// 操作代码}
}
3.性能监控:
// 检测异常频率
try {for (Entry<K,V> e : map.entrySet()) {// ...}
} catch (ConcurrentModificationException ex) {metrics.record("fail-fast.triggered");
}
Fail-Fast机制虽然会给开发者带来一些"麻烦",但它有效地预防了更危险的隐性数据一致性问题,是Java集合框架健壮性的重要保障。
理解这一机制可以帮助开发者写出更安全的集合操作代码。
3、核心结论
在JDK8+的HashMap中:
-
确实存在红黑树退化为链表的机制(当节点数≤6时)
-
这不是红黑树自身的特性,而是HashMap的主动优化
-
转换是安全的,因为这是在扩容(resize)或删除(remove)时触发的
关于树化与退化阈值如下图所示:
4、转化安全机制
HashMap在JDK8引入的红黑树转换机制包含严格的安全保障措施,确保在链表与红黑树相互转换时不会破坏数据一致性和线程安全。
4.1. 触发场景
1. 树化(链表 → 红黑树)条件
使用treeifybin()方法。
// HashMap.treeifyBin() 片段
if (binCount >= TREEIFY_THRESHOLD - 1) { // TREEIFY_THRESHOLD=8if (tab.length < MIN_TREEIFY_CAPACITY) // MIN_TREEIFY_CAPACITY=64resize();elsetreeifyBin(tab, hash);
}
双重校验保障:
-
单链表长度≥8
-
哈希表容量≥64
-
避免小表频繁树化
-
确保有足够分散的桶空间
-
2. 退化(红黑树 → 链表)条件
// HashMap.resize() 片段
if (lc <= UNTREEIFY_THRESHOLD) // UNTREEIFY_THRESHOLD=6tab[index] = loHead.untreeify(map);
安全边界:
-
树节点≤6时才退化(比树化阈值低2,避免频繁转换)
4.2. 转换过程
如下图所示:
1. 链表→红黑树转换流程
关键保障:
-
持有桶头节点锁再进行转换
-
新建TreeNode时保留原链表顺序(通过next指针)
-
平衡操作不改变元素哈希位置
2. 红黑树→链表转换流程
如下图所示:
代码示例:
// TreeNode.untreeify() 实现
final Node<K,V> untreeify(HashMap<K,V> map) {Node<K,V> hd = null, tl = null;for (TreeNode<K,V> q = this; q != null; q = q.next) {Node<K,V> p = map.replacementNode(q, null); // 新建普通节点if (tl == null)hd = p;elsetl.next = p;tl = p;}return hd;
}
安全保障:
-
按原有链表顺序(通过TreeNode保留的next指针)重建
-
新建普通节点而非修改原节点,避免并发访问问题
-
转换完成后原TreeNode可被GC回收
4.3. 并发安全机制
1、转换期间不影响迭代器一致性
abstract class HashIterator {Node<K,V> next; // 下一个返回的节点Node<K,V> current; // 当前节点int expectedModCount; // 修改计数器快照final Node<K,V> nextNode() {if (modCount != expectedModCount)throw new ConcurrentModificationException();// ...}
}
失效保护:
-
迭代期间检测modCount变化
-
快速失败(fail-fast)机制
2、始终维持元素的原始存储顺序
1. 双向链表维护
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent; // 红黑树父节点TreeNode<K,V> left; // 左子树TreeNode<K,V> right; // 右子树TreeNode<K,V> prev; // 链表前驱节点(删除时需要)boolean red;// 仍然保留next指针(继承自Entry)
}
双重结构:
-
红黑树结构:parent/left/right
-
链表结构:next/prev
-
保证在退化时可以快速重建链表
-
支持按插入顺序遍历
-
2. 哈希值不变性
// TreeNode既保持hash值又维持链表顺序
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {return new Node<>(p.hash, p.key, p.value, next);
}
转换过程中始终保持:
-
键的hashCode不变
-
键对象的equals()不变
-
值对象引用不变
3、线程安全(在持有锁的情况下进行)
4、异常处理机制(可进行回滚)
1. 转换失败回滚
try {treeifyBin(tab, hash);
} catch (Throwable t) {tab[index] = originalHead; // 回退到原链表throw t;
}
2. 内存溢出防护
// TreeNode构造时检查内存
if (remaining < treeNodeSpace) {untreeify(); // 立即退化为链表return;
}
HashMap的转换安全机制通过精细的锁控制、结构隔离和状态校验,在保证性能的同时实现了线程安全和数据一致性。
这种设计体现了Java集合框架在高并发场景下的工程智慧,也是为什么HashMap能成为最常用的数据结构之一的关键所在。
5、设计原因
5.1. 性能权衡
数学验证:
-
当n=6时:
-
链表平均查找次数:3次
-
红黑树查找次数:log₂6≈2.58次
-
-
性能差距不大,但红黑树维护成本更高
5.2. 空间局部性
-
链表节点内存连续访问更友好
-
红黑树的树节点结构更复杂:、
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent; // 父节点指针TreeNode<K,V> left; // 左子树指针TreeNode<K,V> right; // 右子树指针TreeNode<K,V> prev; // 前驱节点(仍保留链表结构)boolean red; // 颜色标记
}
5.3. 实际测试数据
在Java标准库的基准测试中:
-
节点数=6时,链表比红黑树快约15%
-
内存占用减少约40%
6、常见误区
1、误区:"红黑树会自动退化为链表"
事实:这是HashMap的主动控制行为。
2、误区:"转换会破坏数据"
事实:元素顺序和内容完全保留。
3、误区:"节点数在7时会频繁转换"
事实:只有在resize/remove时检查阈值。
7、实战建议
-
监控树节点比例
// 检查桶的树化情况
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Node<?,?>[] table = (Node<?,?>[]) tableField.get(map);int trees = 0;
for (Node<?,?> node : table) {if (node instanceof TreeNode) trees++;
}
优化hashCode()
-
减少哈希碰撞可避免树化
-
示例:
// 好的hashCode实现
@Override
public int hashCode() {return Objects.hash(field1, field2, field3);
}
容量规划
// 预设足够大的initialCapacity
new HashMap<>(expectedSize * 2);
总结
JDK的这个设计体现了工程上的精妙权衡:在保持算法理论正确性的同时,针对实际硬件特性和使用场景做出了最优实践选择。
参考文章:
1、HashMap的扩容机制-CSDN博客
相关文章:

JDK8 HashMap红黑树退化为链表的机制解析
目录 1、数据结构: 2、Fail-Fast机制 2.1、核心作用 2.2、实现原理 2.3、触发场景 2.4、实现细节 2.5、对比 2.6、注意事项 3、核心结论 4、转化安全机制 4.1. 触发场景 4.2. 转换过程 4.3. 并发安全机制 5、设计原因 5.1. 性能权衡 5.2. 空间局部性…...

【基础】模型上下文协议(Model Context Protocol, MCP)根本原理与工作机制详解
一、MCP的根本原理 模型上下文协议(MCP)是一种标准化接口协议,旨在解决AI系统(尤其是大型语言模型,LLM)与外部工具、数据源之间的交互碎片化问题。其核心原理可以概括为以下三点: 统一接口抽象…...

霸王茶姬微信小程序自动化签到系统完整实现解析
霸王茶姬微信小程序自动化签到系统完整实现解析 技术栈:Node.js 微信小程序API MD5动态签名 一、脚本全景架构 功能模块图 #mermaid-svg-0vx5W2xo0IZWn6mH {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-s…...
北斗导航 | RTKLib中重难点技术,公式,代码
Rtklib 一、抗差自适应卡尔曼滤波1. **核心难点**2. **公式与代码实现**二、模糊度固定与LAMBDA算法1. **核心难点**2. **LAMBDA算法实现**3. **部分模糊度固定技术**三、伪距单点定位与误差修正1. **多系统多频点修正**2. **接收机钟差与系统间偏差**四、动态模型与周跳处理1.…...

p2p虚拟服务器
ZeroTier Central ✅ 推荐工具:ZeroTier(免费、稳定、跨平台) ZeroTier 可以帮你把多台设备(无论是否跨网)加入一个虚拟局域网,彼此间可以像在同一个 LAN 中通信,UDP 视频、文件传输、SSH 等都…...
Python 爬虫基础入门教程(超详细)
一、什么是爬虫? 网络爬虫(Web Crawler),又称网页蜘蛛,是一种自动抓取互联网信息的程序。爬虫会模拟人的浏览行为,向网站发送请求,然后获取网页内容并提取有用的数据。 二、Python爬虫的基本原…...

python实现点餐系统
使用python实现点餐系统的增加菜品及价格,删除菜品,查询菜单,点菜以及会员折扣价等功能。 代码: 下面展示一些 内联代码片。 # coding utf-8menu {拍黄瓜: 6, 小炒肉: 28, 西红柿炒蛋: 18, 烤鱼: 30, 红烧肉: 38, 手撕鸡: 45,…...

(三)毛子整洁架构(Infrastructure层/DapperHelper/乐观锁)
文章目录 项目地址一、Infrastructure Layer1.1 创建Application层需要的服务1. Clock服务2. Email 服务3. 注册服务 1.2 数据库服务1. 表配置Configurations2. Respository实现3. 数据库链接Factory实现4. Dapper的DataOnly服务实现5. 所有数据库服务注册 1.3 基于RowVersion的…...

探索Stream流:高效数据处理的秘密武器
不可变集合 stream流 Stream流的使用步骤: 先得到一条Stream流(流水线),并把数据放上去 使用中间方法对流水线上的数据进行操作 使用终结方法对流水线上的数据进行操作 Stream流的中间方法 注意1:中间方法࿰…...
git高效杀器——cz-customizable 搭配 commitlint
What is cz-customizable and commitlint? cz-customizable 一款可定制化的Commitizen插件(也可作为独立工具),旨在帮助创建如约定式提交规范的一致性提交消息。commitlint commitlint 是一个用于检查 Git 提交信息的工具,它可以帮助开发者保持提交信息的规范性和一致性。…...

虚拟机ubantu20.04系统桥接模式下无法ping通外网,但可以ping通本机的解决方案
1.出现的问题: 虚拟机ubantu20.04系统桥接模式下无法ping通外网,但可以ping通本机。 2.解决方案: 如果 DHCP 未分配 IP 地址,可以手动配置静态 IP: 1.编辑网络配置文件: sudo nano /etc/netplan/01-netcfg.yaml 2…...

日常知识点之随手问题整理(思考单播,组播,广播哪个更省带宽)
新入职的公司在某些场景下无脑使用组播技术,自己突然就意识到一个问题:单播,组播,广播,哪个更省带宽? 有所收获,做点笔记,仅仅是个人理解~ 1:简单理解 单播࿱…...

qtcreater配置opencv
我配置opencv不管是按照网上的教程还是deep seek发现都有些问题,下面是我的配置方法以及实践成功的心得 电脑环境 windows平台qt6 下载 我这里直接提供官网下载地址:https://opencv.org/releases/ 我下载的是最新版,下载后是一个.exe文件…...
详解 c++17 重载类 overload的每一条语句,附实例.
author: hjjdebug date: 2025年 05月 09日 星期五 16:21:03 CST description: 详解 c17 重载类 overload的每一条语句 文章目录 1. template 模板类.2. class... Ts 是什么意思?3. template<class... Ts> 是什么意思?4. overload 是什么?5. Ts...…...

机器学习-数据集划分和特征工程
一.数据集划分 API函数: sklearn.model_selection.train_test_split(*arrays,**options) 参数: - arrays:多个数组,可以是列表,numpy数组,也可以是dataframe数据框等 - options:&…...

MySQL C API高效编程:C语言实现数据库操作的深入解析
知识点【MySQL C API】 1、头文件及MYSQL * 句柄 //头文件 #include <mysql/mysql.h>1、MYSQL MYSQL是一个结构体,封装了与数据库连接相关的所有状态,配置和数据。 2、MYSQL *的本质 类似于 FILE*,代表一个与数据库连接的通道&…...

MySQL初阶:数据库约束和表的设计
数据库约束 数据库约束是针对数据库中的表中的数据进行施加规则和条件,用于确保数据的准确性和可靠性。 数据库约束类型 1)not null 非空类型 :指定非空类型的列不能存储null,如果插入的数据是null便会报错。 2)de…...

LeetCode 解题思路 47(最长回文子串、最长公共子序列)
解题思路: dp 数组的含义: dp[i][j] 是否为回文子串。递推公式: dp[i][j] s.charAt(i) s.charAt(j) && dp[i 1][j - 1]。dp 数组初始化: 单字符 dp[i][i] true,双字符 dp[i][i 1] s.charAt(i) s.charA…...
左支座加工工艺与钻φ25孔专用夹具设计
1 零件结构与工艺分析 1.1 零件结构特征 本左支座为典型箱体类零件,采用HT200灰铸铁铸造毛坯。主体结构包含: 20015080mm安装基面 2φ12定位孔(公差H7) φ250.02主轴承孔(表面粗糙度Ra1.6) 4M10螺纹安…...
基于Qwen-14b的基础RAG实现及反思
1、概览 本文主要介绍RAG的基础实现过程,给初学者提供一些帮助,RAG即检索增强生成,主要是两个步骤:检索、生成,下面将基于这两部分进行介绍。 2、检索 检索的主要目的是在自定义的知识库kb中查询到与问题query相关的候…...

嵌入式培训之C语言学习完(十七)结构体、共用体、枚举、typedef关键字与位运算
目录 一、结构体(struct关键字) (一)声明一个结构体数据类型 (二)结构体的成员初始化与赋值 a、结构体变量赋值 b、结构体成员初始化 c、结构体的定义形式 (三)考点ÿ…...
极狐GitLab 命名空间的类型有哪些?
极狐GitLab 是 GitLab 在中国的发行版,关于中文参考文档和资料有: 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 命名空间 命名空间在极狐GitLab 中组织项目。因为每一个命名空间都是单独的,您可以在多个命名空间中使用相同的项…...
N6715C 基础型定制配置直流电源分析仪
N6715C 基础型定制配置直流电源分析仪 综述 N6715C 是一款可定制的直流电源分析仪系统,在装运之前已经过全面测试并组装完毕。 每台 N6715C 包括一个 N6705C 主机和 1 至 4 个模块。 模块作为 E6715C 的选件订购。 主要特点 ◆ ◆ 4 插槽主机最多可安装 4 个模块…...
4.1【LLaMA-Factory 实战】医疗领域大模型:从数据到部署的全流程实践
【LLaMA-Factory实战】医疗领域大模型:从数据到部署的全流程实践 一、引言 在医疗AI领域,构建专业的疾病诊断助手需要解决数据稀缺、知识专业性强、安全合规等多重挑战。本文基于LLaMA-Factory框架,详细介绍如何从0到1打造一个垂直领域的医…...

《软件项目经济性论证报告模板:全面解析与策略建议》
《软件项目经济性论证报告模板:全面解析与策略建议》 一、引言 1.1 项目背景阐述 在数字化浪潮席卷全球的当下,各行业对软件的依赖程度日益加深。[行业名称] 行业也不例外,随着业务规模的不断扩张、业务复杂度的持续提升以及市场竞争的愈发激烈,对高效、智能、定制化软件…...
腾讯云:数字世界的“量子熔炉”与硅基文明引擎
一、算力拓扑学:重新定义空间的计算密度 腾讯云的算力网络正在突破经典物理限制,其分布式架构通过“量子化”资源调度实现超维计算: 虚拟化跃迁:基于KVM的轻量级虚拟化技术,将单台物理服务器切割为百…...
Android 项目中配置了多个 maven 仓库,但依赖还是下载失败,除了使用代理,还有其他方法吗?
文章目录 前言解决方案gradlemaven 仓库 前言 我们在Android 开发的过程中,经常会遇到三方依赖下载不下来的问题。一般情况下我们会在项目的build.gradle文件中配置多个 maven 仓库来解决。 // Top-level build file where you can add configuration options com…...

关税冲击下,FBA国际物流企业如何靠智能拓客跑出增长“加速度”?
国际物流行业正迎来前所未有的增长机遇。据中研普华最新报告,2025年全球物流市场规模已突破6.27万亿美元,其中中国跨境物流市场预计达2.71万亿元。在全球化与数字化双轮驱动下,国际物流从“规模扩张”迈向“价值重构”。可以说,国…...

vue源代码采用的设计模式分解
No.大剑师精品GIS教程推荐0地图渲染基础- 【WebGL 教程】 - 【Canvas 教程】 - 【SVG 教程】 1Openlayers 【入门教程】 - 【源代码示例 300】 2Leaflet 【入门教程】 - 【源代码图文示例 150】 3MapboxGL【入门教程】 - 【源代码图文示例150】 4Cesium 【入门教程】…...
【java反射修改注解属性】java 通过反射,动态修改注解的某个属性值
有些情况为了偷懒,往往会使用注解来动态处理一些功能,比如Excel的导入以及导出等。但是一些情况下我们需要动态的修改注解的属性值,来完成一些特定场景的业务需求。 java动态修改注解的属性代码 public void updateFieldAnnotationVal(String…...