Java8的TreeMap源码解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: Java8的TreeMap源码解析

线程不安全,但是因为需要排序,进行key的compareTo方法,所以key是不能null中,value是可以的。


首先庖丁解牛,类似于如何把大象装入冰箱,分三步走:


  1. 以排序二叉树的方式新增节点
    因为红黑树首先本身就是一个排序二叉树
  2. 标记它为红色
    如果设为黑色,就会导致根到叶的路径上有一条路上,多一个额外的黑节点,打破性质 5,这个很难调整
    但设为红色节点后,可能会导致出现两个连续红色节点的冲突,那么可以通过
  3. 颜色调换(color flips)和树旋转
    调整

之后再要进行什么操作就取决于其他临近节点的颜色


注意到:


  • 性质1/2/3总是保持
  • 性质4只在增加红色节点、重绘黑色节点为红色,或做旋转时受到威胁
  • 性质5只在增加黑色节点、重绘红色节点为黑色,或做旋转时受到威胁

在下面的示意图中,


  • 要插入的节点标为N
  • N的父节点标为P
  • N的祖节点标为G
  • N的叔节点标为U

图中展示的任何颜色要么是由它所处情形这些所作的假定,要么就是由假定所自然推出的

插入情境分类

1 N 位于树的根,即无父节点

直接将新插入节点设置为根即可.

这种情形下,把它绘为黑色 - 满足性质2

它在每个路径上对黑节点数目加一 - 满足性质5

2 P 是黑色

直接将N插入即可,不会破坏性质4(N 是红色的).

在这种情形下,性质5未受到威胁,尽管N有两个黑色叶子子节点;但由于N是红色,通过它的每个子节点的路径就都有同通过它所取代的黑色的叶子的路径同样数目的黑色节点,所以依然满足这个性质。


在下面情境下,假定P为红色,所以它有祖节点G.

因为若P是根,则P就应是黑色。所以N总有一个叔节点,尽管在情形4和5下它可能是叶节点


这种情况下会破坏性质 4,所以又分为如下几种情境:

3 P 和 U都是红色

此时N做为P的左孩子或右孩子都属于本情境.

  • 这里仅图解N做为P左孩子的情境

image.png

将G设为红色,P和U设为黑色 - 以保持性质5.


现在N有了一个黑色的父节点P。因为通过父节点P或叔节点U的任何路径都必定通过祖节点G,在这些路径上的黑节点数目没有改变.


But!


红色的祖节点G可能是根,破坏性质2

也可能祖节点G的父节点是红色的,破坏性质4

为了解决这个问题,在祖节点G递归进行情境1.


以下情境,假定P是G的左子节点


4 P是红色,U是黑色或缺少,N是P的右孩子

image.png

左旋P,调换 N 和 P 的角色


这个改变会导致某些路径通过它们以前不通过的N(比如图中的1号叶节点)或不通过P(比如图中3号叶节点),但由于这两个节点都是红色,性质5仍有效


但P和N还是连续的两个红色节点,破坏性质 4还,怎么办?看情境5

5 P是红色,U是黑色或缺少,N是P的左子节点

image.png

操作前G是黑色,否则P不可能是红色(如果P和G都是红色就破坏了性质4)


右旋G,将P设为黑色,G设为红色,达到平衡.此时P是黑色,不用担心P的父节点是红色.


图解完毕,我们来看源码吧!

    public V put(K key, V value) {
      // 根节点
        Entry<K,V> t = root;
        // 若根为空
        if (t == null) {
            compare(key, key); // 类型校验(可能是 null)
      // 创建一个根
            root = new Entry<>(key, value, null);
            // 有一个根元素了
            size = 1;
            // 修改计数器勿忘加一
            modCount++;
            // 返回
            return null;
        }
        // 记录比较结果
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        // 当前使用的比较器
        Comparator<? super K> cpr = comparator;
        // 若比较器字段非空,直接使用指定的比较器
        if (cpr != null) {
          // 循环查找key要插入的位置(也就是新节点的父节点)
            do {
              // 记录上次循环的节点t
                parent = t;
                // 比较当前节点key和新插入节点key的大小
                cmp = cpr.compare(key, t.key);
                // 新key小,以当前节点的左孩子节点为新的比较节点
                if (cmp < 0)
                    t = t.left;
                // 新key大,则以当前节点的右孩子节点为新的比较节点    
                else if (cmp > 0)
                    t = t.right;
                else
                  // 当前节点key和新key相等,则覆盖value并返回
                    return t.setValue(value);
            // 当t为null,即没有要比较节点时,表已找到新节点要插入位置        
            } while (t != null);
        }
        // 比较器为空,使用 key 的比较器
        else {
          // 因此要求key不能为null,并且须实现Comparable接口
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            // 和之前类似,循环查找要插入的位置    
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        // 找到新节点的父节点后,创建节点
        Entry<K,V> e = new Entry<>(key, value, parent);
        // 新节点key值小于父节点key
        if (cmp < 0)
          // 插在父节点左子处
            parent.left = e;
        // 如果新节点key值大于父节点key
        else
          // 插在父节点的右子处   
            parent.right = e;
        // 插入新节点后,为维持平衡,调整红黑树
        fixAfterInsertion(e);
        // 元素数量加一
        size++;
        // 修改计数器加一
        modCount++;
        return null;
    }

下面来看新增节点后对红黑树的调整方法

    private void fixAfterInsertion(Entry<K,V> x) {
        // 将新插入节点的颜色设置为红色
        x.color = RED;
        // 循环保证新插入节点x不是根或其父节点不是红色(这两种情况无需调整)
        while (x != null && x != root && x. parent.color == RED) {
            // 若x的父节点是祖节点的左孩子
            if (parentOf(x) == leftOf(parentOf (parentOf(x)))) {
                // 获取x的叔节点
                Entry<K,V> y = rightOf(parentOf (parentOf(x)));
                // 若x的父节点是红色
                if (colorOf(y) == RED) {
                    // 将x的父节点设为黑色
                    setColor(parentOf (x), BLACK);
                    // 将x的叔叔节点设置为黑色
                    setColor(y, BLACK);
                    // 将x的祖节点设为红色
                    setColor(parentOf (parentOf(x)), RED);
                    // 将x指向祖节点
                    // 若x的祖节点的父节点是红色,按照之前流程继续循环
                    x = parentOf(parentOf (x));
                } else {
                    // 若x的叔节点是黑色或缺少,且x的父节点是祖节点的右孩子
                    if (x == rightOf( parentOf(x))) {
                        // 左旋父节点
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    // 若x的叔叔节点是黑色或缺少,且x的父节点是祖父节点的左孩子
                    // 将x的父节点设为黑色
                    setColor(parentOf (x), BLACK);
                    // 将x的祖节点设为红色
                    setColor(parentOf (parentOf(x)), RED);
                    // 右旋x的祖节点
                    rotateRight( parentOf(parentOf (x)));
                }
            // 若x的父节点是祖节点的右孩子,流程和上面类似,只是左旋右旋区分,不再赘述    
            } else {
                Entry<K,V> y = leftOf(parentOf (parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf (x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf (parentOf(x)), RED);
                    x = parentOf(parentOf (x));
                } else {
                    if (x == leftOf( parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf (x), BLACK);
                    setColor(parentOf (parentOf(x)), RED);
                    rotateLeft( parentOf(parentOf (x)));
                }
            }
        }
        // 最后将根设置为黑色,反正根节点就得是黑色
        root.color = BLACK;
    }

下面来看下左旋和右旋的代码

    /**
     * 左旋示意图:
     *      px                              px
     *     /                               /
     *    x                               y               
     *   /  \      --(左旋)--            /  \                
     *  lx   y                          x  ry    
     *     /   \                       /  \
     *    ly   ry                     lx  ly 
     *
     */
    private void rotateLeft(Entry<K,V> p) {
        if (p != null) {
            //  获取p的右孩子
            Entry<K,V> r = p.right;
            // 将 r的左孩子 设为 p的右孩子
            p.right = r.left ;
            // 若r的左孩子非空,将p设为r的左孩子的父
            if (r.left != null)
                r.left.parent = p;
            // 将p的父设为y的父
            r.parent = p.parent;
            // 若p的父为空,则将r设为根
            if (p.parent == null)
                root = r;
            // 若p是其父的左孩子,则将r设为p的父节点的左孩子
            else if (p.parent.left == p)
                p.parent.left = r;
            else             
                // 若p是其父的右孩子,则将r设为p的父节点的右孩子
                p.parent.right = r;
            // 将p设为r的左孩子
            r.left = p;
            // 将p的父设为r
            p.parent = r;
        }
    }
    /**
     * 右旋示意图(对节点y右旋):
     *            py                               py
     *           /                                /
     *          y                                x                 
     *         /  \      --(右旋)--             /  \                     
     *        x   ry                           lx  y 
     *       / \                                  / \                   
     *      lx  rx                               rx  ry
     *
     */
    private void rotateRight(Entry<K,V> p) {
        if (p != null) {
            // 获取p的左孩子
            Entry<K,V> l = p. left;           
            // 将l的右孩子设为p的左孩子
            p.left = l.right ;
            // 若l的右孩子非空,将p设为l的右孩子的父
            if (l.right != null) l. right.parent = p;
            // 将p的父设为l的父
            l.parent = p.parent ;
            // 若p的父为空,则将l设为根
            if (p.parent == null)
                root = l;      
            // 若p是其父的右孩子,则将l设为p的父节点的右孩子
            else if (p.parent.right == p)
                p.parent.right = l;
            // 若p是其父节点的左孩子,将l设为p的父节点的左孩子
            else p.parent.left = l;
            // 将p设为l的右孩子
            l.right = p;
            // 将l设为p父节点
            p.parent = l;
        }
    }
目录
相关文章
|
14天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
50 7
|
7天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
52 13
|
20天前
|
Java 编译器
Java 泛型详细解析
本文将带你详细解析 Java 泛型,了解泛型的原理、常见的使用方法以及泛型的局限性,让你对泛型有更深入的了解。
30 2
Java 泛型详细解析
|
16天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
20天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
50 12
|
15天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
17天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
17天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
20天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
16天前
|
人工智能 移动开发 安全
家政上门系统用户端、阿姨端源码,java家政管理平台源码
家政上门系统基于互联网技术,整合大数据分析、AI算法和现代通信技术,提供便捷高效的家政服务。涵盖保洁、月嫂、烹饪等多元化服务,支持多终端访问,具备智能匹配、在线支付、订单管理等功能,确保服务透明、安全,适用于家庭生活的各种需求场景,推动家政市场规范化发展。

推荐镜像

更多
下一篇
DataWorks