Java HashMap从链表到红黑树的"进化"过程详解

 更新时间:2026年02月05日 11:30:15   作者:予枫的编程笔记  
在Java集合框架中,HashMap的底层实现在JDK 1.8迎来了一次重大革新:引入了红黑树,本文将结合底层源码,带你彻底搞懂HashMap是在什么条件下、如何进行树化的,感兴趣的朋友跟随小编一起看看吧

在 Java 集合框架中,HashMap 的底层实现在 JDK 1.8 迎来了一次重大革新:引入了红黑树。这一设计并非为了酷炫,而是为了解决哈希碰撞导致的性能退化问题。本文将结合底层源码,带你彻底搞懂 HashMap 是在什么条件下、如何进行树化的。

一、 核心源码常量定义

HashMap.java 中,有三个关键常量决定了树化与退化的阈值:

/**
 * 1. 树化阈值:当桶中链表长度大于该值时,尝试转为红黑树
 */
static final int TREEIFY_THRESHOLD = 8;
/**
 * 2. 退化阈值:当扩容或删除节点导致树节点数小于该值时,转回链表
 */
static final int UNTREEIFY_THRESHOLD = 6;
/**
 * 3. 最小树化容量:只有当数组总容量大于该值时,才会真正进行树化
 */
static final int MIN_TREEIFY_CAPACITY = 64;

二、 树化的“双重条件”深度逻辑

很多开发者只记得“链表长度 > 8”,但实际上源码中存在一个隐藏的判定逻辑

1. 触发入口:putVal方法

当我们在 put 一个元素时,如果发生碰撞且当前是链表结构,会进入以下逻辑:

// JDK 1.8 putVal 部分源码
for (int binCount = 0; ; ++binCount) {
    if ((e = p.next) == null) {
        p.next = newNode(hash, key, value, null); // 插入新节点(尾插法)
        if (binCount >= TREEIFY_THRESHOLD - 1) // 如果链表长度达到 8
            treeifyBin(tab, hash); // 尝试树化
        break;
    }
    // ... 忽略省略部分
}

2. 核心判定:treeifyBin方法

进入 treeifyBin 后,并不是直接转红黑树,它会先检查数组的长度:

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    // 【核心判定】
    // 如果数组为空,或者数组长度 n < 64,则优先选择扩容而不是树化
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize(); 
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        // 只有数组长度 ≥ 64 且链表长度 > 8,才会执行真正的树化逻辑
        // ... 将 Node 转换为 TreeNode 的过程
    }
}

三、 深度思考:背后的数学与工程考量

1. 为什么是 8?—— 泊松分布

根据 HashMap 源码注释,节点在哈希桶中的频率遵循泊松分布。在负载因子为 0.75 的情况下,链表长度达到 8 的概率极低,约为 0.00000006。

设计用意:正常情况下,我们几乎不会遇到树化。红黑树是为了应对那些哈希函数设计不佳,甚至遭受恶意哈希攻击导致大量碰撞的情况。

2. 为什么退化阈值是 6 而不是 7?

这是为了留出缓冲区。如果退化阈值也是 8,那么当一个桶的节点数在 7 和 8 之间反复变动时,会引起频繁的“树化 <-> 退化”转换。这会导致大量的 TreeNodeNode 对象的创建与销毁,严重影响性能。

3. 节点结构的巨大变化

树化不仅仅是逻辑变了,底层存储的对象类型也发生了质变:

  • 链表节点 (Node):包含 hash, key, value, next
  • 树节点 (TreeNode):继承自 LinkedHashMap.Entry,除了基本属性,还增加了 parent, left, right, prev, red(红黑属性)。

空间代价TreeNode 占用的内存空间大约是普通 Node2 倍

四、 总结:HashMap 的进化准则

链表转红黑树:当前桶链表长度

 且数组总容量

红黑树转链表:在扩容或删除元素时,若树中节点数

  • 核心哲学
    • 容量小、碰撞多:通过 resize 扩容来平摊碰撞。
    • 容量大、碰撞多:通过 treeify 提升查询效率(从 O(n) 降至 O(log n))。

💡 面试贴士

在面试中,如果面试官问:“HashMap 什么时候树化?”,完整的回答应该是:

“当链表长度超过 8 时,HashMap 会调用 treeifyBin 方法。但该方法内部会先判断数组容量,如果容量小于 64,会优先扩容;只有容量大于等于 64 且链表长度达到 8,才会正式转换为红黑树。”

到此这篇关于深入浅出 Java HashMap:从链表到红黑树的“进化”之路的文章就介绍到这了,更多相关Java HashMap链表到红黑树内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 深入理解Java中的类加载器原理

    深入理解Java中的类加载器原理

    这篇文章主要介绍了深入理解Java中的类加载器原理,类加载器负责加载所有的类,系统为所有被载入内存中的类生成一个java.lang.Class实例,需要的朋友可以参考下
    2024-01-01
  • 值得收藏的SpringBoot 实用的小技巧

    值得收藏的SpringBoot 实用的小技巧

    最近分享的一些源码、框架设计的东西。我发现大家热情不是特别高,想想大多数应该还是正儿八经写代码的居多;这次就分享一点接地气的: SpringBoot 使用中的一些小技巧 ,需要的朋友可以参考下
    2018-10-10
  • 详解如何判断Java线程池任务已执行完

    详解如何判断Java线程池任务已执行完

    线程池的使用并不复杂,麻烦的是如何判断线程池中的任务已经全部执行完了,所以接下来,我们就来看看如何判断线程中的任务是否已经全部执行完吧
    2023-08-08
  • SpringCloud Zuul和Gateway的实例代码(搭建方式)

    SpringCloud Zuul和Gateway的实例代码(搭建方式)

    本文主要介绍了SpringCloudZuul和SpringCloudGateway的简单示例,SpringCloudGateway是推荐使用的API网关解决方案,基于SpringFramework5和ProjectReactor构建,具有更高的性能和吞吐量
    2025-02-02
  • 详解java构建者模式Builder

    详解java构建者模式Builder

    这篇文章主要介绍了java构建者模式Builder,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • springboot jackson配置教程

    springboot jackson配置教程

    这篇文章主要介绍了springboot jackson配置教程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • 详解JAVA中static的作用

    详解JAVA中static的作用

    这篇文章主要介绍了JAVA中static的作用,文中讲解非常细致,代码帮助大家更好的理解,感兴趣的朋友可以了解下
    2020-06-06
  • maven国内镜像配置的方法步骤

    maven国内镜像配置的方法步骤

    这篇文章主要介绍了maven国内镜像配置的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • Java面试最容易被刷的重难点之锁的使用策略

    Java面试最容易被刷的重难点之锁的使用策略

    锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂。因为锁(以及其它更高级的线程同步机制)是由synchronized同步块的方式实现的,所以我们还不能完全摆脱synchronized关键字
    2021-10-10
  • Java中BigDecimal使用注意避坑指南

    Java中BigDecimal使用注意避坑指南

    Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算,下面这篇文章主要给大家介绍了关于Java中BigDecimal使用注意避坑的相关资料,需要的朋友可以参考下
    2023-02-02

最新评论