【Java集合】TreeSet、TreeMap源码解读

 参考笔记:java TreeSet 和 TreeMap 源码解读-CSDN博客


目录

1.前言

2.红黑树

2.1 红黑树的五大性质

2.2 节点颜色的初始设置

2.3 插入新节后的调整

2.4 删除结构后的调整

2.5 排序规则

2.6 设计红黑树的原因

3.TreeSet简介、底层实现

3.1 TreeSet简介

3.2 TreeSet底层实现

4. TreeSet源码解读:演示第一种排序机制:元素类实现Comparable接口

        4.1 准备工作

        4.2 跳入TreeSet无参构造器 

        4.3 向集合中添加第一个元素100 

                ① 跳入add 方法

                ② 跳入put方法

                ③ 逐层返回,跳回演示类中

        4.4 向集合中添加第二个元素 3

                ① 跳入put方法

                ② 逐层返回,跳回演示类中

        4.5 向集合中添加重复元素3

                ① 跳入put方法

                ② 回到add方法

                ③ 回到演示类中

        4.6 继续向集合添加元素

        4.7 输出结果

5. TreeSet源码解读:演示第二种排序机制:提供Comparator比较器

        5.1 准备工作

        5.2 跳入TreeSet有参构造器

        5.3 向集合添加第一个元素 100

        5.4 向集合添加第二个元素3

                ① 跳入put方法

                ② 逐层返回,跳回演示类中

        5.5 继续向集合添加元素

        5.6 输出结果

6.TreeMap简介+底层实现

7.TreeMap源码解读

        7.1 准备工作

        7.2 TreeMap构造器

        7.3 向集合添加第一个元素

        7.4 向集合添加第二个元素

        7.5 输出结果

8.完结


1.前言

        本文将通过 Debug 流程分析,剖析 TreeSet 以及 TreeMap 的底层实现。TreeSet 的底层基于  TreeMap 实现,而 TreeMap 使用 红黑树 Red-Balck Tree 作为数据结构。因此本文在对 TreeSet、TreeMap 源码分析之前会先介绍一下红黑树的基本知识
        注意:本篇博文对 HashSet 源码的解读基于主流的 JDK 8.0 的版本

2.红黑树

红黑树(Red-Black Tree)是一种自平衡二叉搜索树,它通过特定的颜色规则和调整操作来保持树的平衡

2.1 红黑树的五大性质

 颜色属性:每个节点要么是红色,要么是黑色

② 根节点属性:根节点必须是黑色

 红色节点属性:红色节点的子节点必须是黑色,即不能有两个连续的红色节点

④ 叶子节点属性:所有叶子节点(NIL节点,空节点)都是黑色

 黑高一致性:从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点,称为"黑高"

补充:红黑树的叶子节点与普通二叉树叶子节点的区别

  • 普通二叉树:没有子节点的节点为叶子节点

  • 红黑树:所有实际数据节点都被视为内部节点,真正的叶子节点是虚拟的NIL/NULL节点

那为什么红黑树的叶子节点要这样设计呢?

  • 简化边界条件处理:将所有空指针都视为统一的 NIL 节点

  • 保持规则一致性:

    • 确保"从节点到叶子路径的黑高相同"规则可以统一应用

    • 确保"红色节点的子节点必须是黑色"规则可以统一检查

  • 代码实现方便


2.2 节点颜色的初始设置

  • 新插入的节点:默认总是设置为红色

    • 原因:设置为红色不会违反红黑树的性质⑤:黑高一致性

    • 风险:可能会违反"无连续红色节点"规则(性质③)

  • 特殊情况:如果插入的是根节点,会强制变为黑色(满足性质②)

2.3 插入新节后的调整

插入的新节点初始是红色,也就是 "红插" ,后续将根据平衡性变色和调整,规则如下:

① 新节点的父节点是黑色:不需要任何调整,没有违反红黑树的五大性质

② 新节点的父节点是红色:

  • 需要根据叔叔节点(父节点的兄弟)的颜色进一步处理:

    • 叔叔节点是黑色:

      • LL型:右单旋,父换爷+变色

      • RR型:左单旋,父换爷+变色

      • LR型:左右双旋,儿换爷+变色

      • RL型:右左双旋,儿换爷+变色

    • 叔叔节点是红色:

      • 叔父爷节点先全部变色,爷爷节点变成新节点,然后再来一轮红黑树调整

关于红黑树的旋转、调整等其他操作大家可以去在线演示网站试试,有助于理解,链接如下:

Red/Black Tree Visualization https://www.cs.usfca.edu/~galles/visualization/RedBlack.html

2.4 删除结构后的调整

删除节点后可能会破坏黑高一致性,需要通过复杂的旋转和重新着色来恢复平衡,本文不作探讨

2.5 排序规则

left < root < right
  • 左子树规则:左子树中的所有结点都小于根节点

  • 右子树规则:右子树中的所有结点都大于根节点

2.6 设计红黑树的原因

  🆗,下面开始本文的正式内容,TreeSet、TreeMap的源码解析

3.TreeSet简介、底层实现

3.1 TreeSet简介

TreeSet 是单列集合 Set 接口的一个实现类,位于 java.util.TreeSet 下,其类定义和继承关系图如下所示:

TreeSet 中的元素不能为 null ,否则会报 NullPointerException              
HashSet 类不同, TreeSet 最大的特点是可以进行排序TreeSet 底层基于 TreeMap,而 TreeMap 底层由红黑树实现,可以对元素对象进行排序

  • 排序机制要求:

    • 要么元素类实现 Comparable 接口,重写 compareTo 方法,自定义排序逻辑

    • 要么在创建 TreeSet 集合时提供 Comparator 比较器并重写 compare 方法,自定义排序逻辑

3.2 TreeSet底层实现

TreeSet 的底层是 TreeMap ,而 TreeMap 是使用红黑树数据结构实现的

TreeSet 中的每个结点类型是 TreeMap$EntryEntryTreeMap 的一个静态内部类,其类的定义如下:

TreeSet 的去重机制:

        (1)如果使用 TreeSet 的无参构建创建 TreeSet 集合,并且添加的元素类已经实现了Comparable 接口的 compareTo 方法,则使用 compareTo 方法去重。即如果 compareTo 方法返回值为 0 ,则认为是相同的元素或数据,放弃添加

        (2)如果使用 TreeSet 的有参构造创建 TreeSet 集合时提供了 Comparator 比较器,就使用重写的 compare 方法进行去重。即如果 compare 方法返回值为 0 ,则认为是相同的元素或数据,放弃添加

注意:如果既没有在创建 TreeSet 对象时提供 Comparator 比较器,也没有在所添加的元素的类中重写 Comparable 接口的 compareTo 方法,那么在添加元素时 JVM 一定会报类型转换异常 ClassCastException

示例代码:

import java.util.TreeSet;
public class demo {public static void main(String[] args) {System.out.println("hello world");TreeSet treeSet = new TreeSet();treeSet.add(new Cat());}
}class Cat {//none
}

运行结果:

TreeSet 的底层是 TreeMap ,那肯定会需要频繁访问 TreeMap 中的属性或者方法,那 Java 中是如何实现的呢?

TreeSet 类中维护了一个属性 private transient NavigableMap<E,object>  m,初始值为 nullTreeSet 就是用 m 来访问 TreeMap 中的属性或方法的,其源码如下:

但 NavigableMap 是一个接口类型,看到这大家肯定会觉得懵逼,前面不是说要用 m 来访问 TreeMap 吗? 这类型都不一致,怎么访问啊?别急,我们来看看  TreeMapNavigableSet 的关系,如下图:

🆗,可以看到,TreeMap 类 实现了 NavigableMap 接口,所以接口 NavigableMap 引用可以指向 TreeMap 对象,只能说多态机制牛逼!!!TreeSet 底层正好就是这么做的,在使用 TreeSet 构造器创建 TreeSet 集合对象时,会给 private transient NavigableMap<E,object>  m 作初始化,下面我以无参构造器为例:

所以 private transient NavigableMap<E,object>  m 最终的运行类型就是 TreeMap,这样TreeSet 对象内部就可以通过这个 m 去访问 TreeMap 中的属性或者方法了。 还是想感叹 Java多态机制真牛逼!!!

4. TreeSet源码解读:演示第一种排序机制:元素类实现Comparable接口

        4.1 准备工作

        我们这里的案例是向 TreeSet 集合中添加 int 类型,但底层会实现自动装箱 int ---> Integer。而 Integer 类刚好实现了 Comparable 接口,并实现 CompareTo 方法,如下: 

        Integer 实现的 compareTo 方法内部调用了 compare 方法。compareTo 方法在底层会作为该红黑树的排序机制和去重机制

        最终该红黑树的排序规则如下:

left < root < right

        最终打印 TreeSet 集合的效果就是按升序排序

        去重机制:当前添加的元素与某个结点使用 compareTo 方法比较时,如果返回值 = 0,则为重复元素,放弃添加

           🆗,解释完毕后,将用以下代码作为演示类,一步一步 Debug 

import java.util.TreeSet;
public class demo {public static void main(String[] args) {TreeSet treeSet = new TreeSet();treeSet.add(100);treeSet.add(3);treeSet.add(3);//重复元素,放弃添加treeSet.add(50);treeSet.add(44);System.out.println("treeSet = " + treeSet);//[3,44,50,100]}
}

        4.2 跳入TreeSet无参构造器 

                首先跳入 TreeSet 的带参构造,如下 :  

                我们先看上图中的,调用的是 TreeMap 的无参构造,无参构造内有一个赋值语句 comparator = nullcomparatorTreeMap 类维护的一个属性,用来接收传入的 Comparator 比较器,作为 TreeSet 集合的排序机制和去重机制。其源码如下:

                由于我们并没有传入 Comparator 比较器,所以 comparator = null

                接着 ② 则是调用 TreeSet 的一个有参构造器 TreeSet(NavigableMap<E,Object> m) ,有参构造器内也是一个赋值于语句:this.m = m,为 TreeSetm 属性赋值。这里的 m 我们在前文的 "TreeSet底层实现" 中提到过,TreeSet 就是通过 m 来访问 TreeMap 中的属性和方法的,其源码如下:

                OK,我们跳出 TreeSet 无参构造器,此时的 TreeSet 集合状态如下:

                可以看到,正如我们在 "TreeSet底层实现" 所说的,m 的类型确实是 TreeMapTreeMap 有挺多属性的,但我们只需要关注上图中绿色框部分。由于当前集合未添加元素,所以此时的 root = null,size = 0,modCount = 0 

        4.3 向集合中添加第一个元素100 

                ① 跳入add 方法

                向 TreeSet 集合中添加第一个元素 100 ,跳入 add 方法,这里会先进入 valueOf 方法进行装箱操作 int ---> Integer,我们直接跳出,直接重新进入 add 方法,如下所示 : 

                可以看到,底层走的是 TreeMap 的 put 方法;注意实参中的 PRESENT, 这个PRESENTHashSetPRESENT 是一样的,只起到占位的作用,其源码如下 :  

                ② 跳入put方法

                继续往下执行,跳入 put 方法,其 源码+注释 如下所示,非常详细:

//添加元素到treeSet集合中
//key:元素值 value:PRESENT占位符
public V put(K key, V value) {//记录红黑树的根结点TreeMap.Entry<K,V> t = root;/** 集合元素个数 = 0 ,执行 if 语句 **///添加第一个元素时,t = null,会执行该 if 语句if (t == null) {compare(key, key); //健壮性判断,这一行可以不用管//创建一个新结点作为根结点root = new TreeMap.Entry<>(key, value, null);size = 1;//集合元素+1modCount++;//集合修改次数+1return null;//返回null表示添加成功}/** 集合中元素个数 >= 1,执行以下代码 **/int cmp;//记录元素比较结果TreeMap.Entry<K,V> parent;//将comparator属性赋值给cpr,comparator为比较器Comparator<? super K> cpr = comparator;//如果在创建TreeSet集合对象时传入了比较器,则cpr必不为空,会执行该if语句if (cpr != null) {/*do while循环的作用:利用比较器cpr中的compare方法作为作为排序逻辑,依次遍历红黑树,判断当前添加的元素是否重复:(1)如果不重复,则为当前添加的元素找到合适的存放位置,最终parent即为添加元素的父结点(2)如果重复,则放弃添加*/do {parent = t;cmp = cpr.compare(key, t.key);//比较欲添加的元素和当前结点t的keyif (cmp < 0)t = t.left;//往左子树查找存放位置else if (cmp > 0)t = t.right;//右右子树查找存放位置else//如果cmp = 0 ,说明添加的元素与当前结点t重复,放弃添加return t.setValue(value);} while (t != null);}//如果在元素类中实现了Comparable接口的compareTo方法,则会执行该else语句else {if (key == null)throw new NullPointerException();@SuppressWarnings("unchecked")Comparable<? super K> k = (Comparable<? super K>) key;/*do while循环的作用:利用元素类中实现的Comparable接口的compareTo方法作为排序逻辑,依次遍历红黑树,判断当前添加的元素是否重复(1)如果不重复,则为当前添加的元素找到合适的存放位置,最终parent即为添加元素的父结点(2)如果重复,则放弃添加*/do {parent = t;cmp = k.compareTo(t.key);//比较欲添加的元素和当前结点t的keyif (cmp < 0)t = t.left;//往左子树查找存放位置else if (cmp > 0)t = t.right;//往右子树查找存放位置else//如果cmp = 0 ,说明添加的元素与当前结点t重复,放弃添加return t.setValue(value);} while (t != null);}/**若不是第一次往TreeSet中添加元素,且能执行到这里,说明为当前欲添加的元素不重复,为其找到了合适的存放位置**///将添加的元素包装在Entry类中,记录key:添加元素,value:PRESENT占位符,parent:父结点TreeMap.Entry<K,V> e = new TreeMap.Entry<>(key, value, parent);//根据与父结点的比较结果cmp来决定存放在父节点的左子树还是右子树if (cmp < 0)parent.left = e;elseparent.right = e;//插入新结点后,需要调用fixAfterInsertion函数来完成红黑树中结点的颜色调整、旋转等fixAfterInsertion(e);//更新size:集合中的元素个数 modCount:修改集合的次数size++;modCount++;//添加成功,返回nullreturn null;
}

                这里我还是一步一步 Debug 过一遍,等整个流程搞清楚再看上面的 源码+注释 会更容易理解一点

                我们是向 TreeSet 集合中添加第一个元素 100,所以此时的 root = null ,只会执行上图中的红框部分,即 if 结构

                if 结构中的第一行 compare 方法不需要理会,它只是作一个健壮性的判断。接下来就是root = new Entry<>(key,value,null),即将当前添加的元素用 Entry 类包装起来,这里我们跳进去看看该构造器方法,如下:

                 可以看到,构造器中对 Entry 类的 key、value、parent 的属性进行赋值,key(存放添加元素):100;value:PRESENT占位符,无实际意义;parent(父结点):null;还有一个比较关键的地方就是 color = BLACK(由红黑树的五大性质可知,根节点是黑结点

                OK,重新回到 put 方法,继续往下执行,更新集合元素个数 size = 1 ,更新集合修改次数 moCount ++ 

                最后 return null 表示添加元素成功,跳出 put 方法

                ③ 逐层返回,跳回演示类中

                逐层返回,回到演示类中,第一个元素 100 添加成功,查看此时的 TreeSet 集合的状态,如下:

                可以看到,第一个元素 100 添加成功,且作为根节点

        4.4 向集合中添加第二个元素 3

                ① 跳入put方法

                对于第二个元素的添加,我们直接跳到 put 方法,如下 : 

                首先,由于此时 root != null,所以 t != null,不会执行第一个 if 语句

                接着,是两个变量的定义:

                定义了一个 int 型变量 cmpEntry 类型变量 parent。这里先不作解释,后面用到再说

                继续,往下执行, 如下所示:

               由于我们并没有在创建 TreeSet 集合对象时传入比较 Comparator ,所以 comparator = cpr = null,所以不会进入该 if 结构中

                接着,重点的来了,如下图所示:

                首先,先判断添加的元素 key 是否为 null ,如果为 null 则抛出空指针异常  NullPointerException

                接着,进入非常关键的 do while 循环,do while 循环的作用如下:

利用元素类 Integer 中实现的 Comparable 接口的 compareTo 方法作为排序机制和去重机制,依次遍历红黑树,判断当前添加的元素是否重复:

        (1)如果不重复:则为当前添加的元素找到合适的存放位置,最终 parent 即为添加元素的父节点

        (2)如果重复:放弃添加

                我们当前添加的元素为 3 ,显然不会重复,经过 do while 循环,最终 parent 是元素 3 的父结点,我们看一下元素 3 的父节点是谁:

                可以看到,元素 3 的父节点是我们刚才添加的第一个元素 100

                前面的代码只是为元素 3 找到了合适的存放位置,即找到了元素 3 的父节点,但还未真正将元素 3 加入到集合中,所以继续往下执行,如下所示:

                第一行即将当前添加的元素 3 Entry 类包装起来,调用的构造器我们在前面也有提到过,这里就不再赘述了。重点是下面的 if --- else 结构,下面的 if---else 结构的作用:根据当前添加的元素与其父节点 parent 的比较结果 cmp 来决定存放在父节点的左子树还是右子树。由于当前添加的元素是 3 ,父节点 parent100,因此 cmp 返回值为 -1 ,执行 if 语句,如下所示:

                所以,元素 3 存放在 100左结点

                OK,到这别忘了,因为我们在红黑树中插入了一个新结点,而红黑树又要遵守其五大性质,所以可能需要进行红黑树结点颜色的调整、旋转等,那源码中如何实现的呢?

                很简单,Java 是将红黑树结点颜色的调整、旋转等问题包装成一个叫做 fixAfterInsertion函数,如下:

                见名知意,fixAfterInsertion 的意思就是 "修复插入之后的问题" 。可以看到, fixAfterInsertion 方法的第一行 x.color = RED 就是将新插入的结点颜色设置为红色,之后再通过一个 while 循环判断是否需要对红黑树的结点颜色进行调整和旋转,源码比较复杂,感兴趣的朋友可以自己看一下

                最后,执行完 fixAfterInsertion 函数就是更新集合元素个数 size 、更新集合修改次数modCountreturn null 表示添加元素成功

                ② 逐层返回,跳回演示类中

                执行完 put 方法后,逐层返回,回到演示类,第二个元素 3 添加成功,查看此时的 TreeSet 集合的状态,如下:

                可以看到,添加的元素 3 确实存放在元素 100 的左节点 left ,此时的红黑树结构如下图所示:

        4.5 向集合中添加重复元素3

        当前我们演示的是第一种排序机制:元素类实现 Comparable 接口,所以 TreeSet 的去重机制是根据实现的 Comparable 接口的 compareTo 方法来实现的。即两个元素利用 compareTo 方法进行比较时,如果返回值 = 0,说明元素重复,放弃添加

                ① 跳入put方法

                同样地,前面一些相同的步骤我们不再赘述,我们直接跳到 put 方法中判断重复元素的位置,如下:

                do while 循环的作用我们在上文中已经说明,忘记了可以往前翻一下。这里由于当前添加的元素 3 为重复元素,所以一定会进入 do while 循环中的 else 部分,如下:

                可以看到,else 部分执行的语句是 return t.setValue(value) setValue 方法中其实就是将重复结点 t valuePRESENT占位符)重复赋值为PRESENT占位符,其实相当于什么都没做,然后 return oldValue = return PRESENT ,所以 put 方法如果添加的是重复元素会 return t.setValue(value) = return PRESENT

                ② 回到add方法

                put 方法执行完毕,回到 add 方法,由于添加的是重复元素,所以 add 方法的返回值一定为 false ,如下所示:

                ③ 回到演示类中

                add 方法执行完毕,回到演示类中,重复元素 3 放弃添加,查看此时的 TreeSet 集合的状态,如下:

                可以看到,集合元素个数 size = 2,元素更改次数 modCount = 2,说明重复元素 3 确实没有加入到 TreeSet 集合中

        4.6 继续向集合添加元素

        继续添加元素 50、44,由于排序机制、查找元素存放位置、去重机制前面已经讲的很详细了,所以这里直接看添加元素 55、44 之后的集合状态,如下所示:

         可以看到,根节点 root 变成了元素 50 ,集合元素个数 size = 4,集合更改次数 modCount  = 4,此时的红黑树结构如下所示:

        每个结点满足 left < root < right 

        4.7 输出结果

         treeSet 集合输出结果如下:

        可以看到,System.out.print(treeSet) 输出的是 treeSet 集合的升序排序结果

🆗, TreeSet 第一种排序机制的源码分析完毕,下面进行 TreeSet 第二种排序机制的源码分析

5. TreeSet源码解读:演示第二种排序机制:提供Comparator比较器

        5.1 准备工作

        我们这里的案例仍然是向 TreeSet 集合中添加 int 类型,底层会实现自动装箱 int ---> Integer。但不再使用 Integer 类实现的 Comparable 接口的 compareTo 方法,而是给 TreeSet 传入一个 Comparator 比较器,在比较器中重写 compare 方法,作为 TreeSet排序机制和去重机制

        🆗,将用以下代码作为演示类,一步一步 Debug

import java.util.Comparator;
import java.util.TreeSet;
public class demo {public static void main(String[] args) {TreeSet treeSet = new TreeSet(new Comparator<Integer>() {@Overridepublic int compare(Integer x, Integer y) {//该比较器可以实现一个降序排序的效果if (x < y){return 1;}else if (x > y){return -1;}else{return 0;}//三目运算符的写法:return (x < y) ? 1 : ((x == y) ? 0 : -1);}});treeSet.add(100);treeSet.add(3);treeSet.add(3);//重复元素,放弃添加treeSet.add(50);treeSet.add(44);System.out.println("treeSet = " + treeSet);//[100,50,44,3]}
}

        为了更好的与第一种排序机制比较,演示代码向 TreeSet 集合中添加的是元素同样是 100、3、3、50、44,不同之处在于我们提供的 Comparator 比较器实现的是降序排序

        该红黑树的排序规则如下:

left > root > right

        去重机制:当前添加的元素与某个结点使用 Comparator 比较器中重写的 compare 方法比较时,如果返回值 = 0,则为重复元素,放弃添加 

           最终 System.out.println("treeSet = " + treeSet)  的输出结果是:treeSet = [100,50,44,3]

        5.2 跳入TreeSet有参构造器

        首先我们跳入 TreeSet 的带参构造,如下 :  

        先不着急跳入 TreeMap 的构造器,先看一下 TreeSet 带参构造的形参:是一个 Comparator(比较器)类型的变量,这个 Comparator 类型其实就是一个接口,其源码如下 : 

        而我们一开始在演示类中传入的是这个玩意:

    new Comparator<Integer>() {@Overridepublic int compare(Integer x, Integer y) {//该比较器可以实现一个降序排序的效果if (x < y){return 1;}else if (x > y){return -1;}else{return 0;}//三目运算符的写法:return (x < y) ? 1 : ((x == y) ? 0 : -1);}}

        其实就是一个实现了 Comparator 接口的匿名内部类对象,并在 TreeSet 带参构造中形成了多态。而在 TreeSet 带参构造中,又将匿名内部类对象 comparator 传递给了 TreeMap 的一个带参构造
        匿名内部类中实现了 Comparator 接口中的 compare 方法,该方法决定了 TreeSet 集合的排序机制和去重机制

        🆗,解释清楚了,我们重新回到下面这张图:

        看 ,追入 TreeMap 的带参构造器,如下图所示:

        可以看到,在 TreeMap 构造器中,将匿名内部类对象 comparator 传递给了 TreeMap 的一个属性 comparator。并且,此时的 comparator 已经显示为了匿名内部类 "demo$1" 的形式
        接着看 ,仍然是将 new 好的 TreeMap 对象赋值给 TreeSet m 属性

        5.3 向集合添加第一个元素 100

        向集合中添加第一个元素 100 的流程步骤和演示第一种排序机制的时候是完全一致的,所以这里我以 GIF 展示,如下所示:

        可以看到,第一个元素 100 成功添加到 TreeSet 集合中,且作为 root 根结点

        5.4 向集合添加第二个元素3

                ① 跳入put方法

                我们直接跳到 put 方法,如下所示:

                这时候 t 显然不为空,不会进入第一个 if 语句

                继续往下执行,重点来了,如下:  

                仔细观察上图中的红色底线部分,底层是利用匿名内部类中重写的 compare 方法来比较欲添加的元素和当前结点 t key 。根据动态绑定机制,这时候如果我们跳入 compare 方法,一定会跳到演示类中的匿名内部类, 如下图所示 :  

                所以 TreeSet 确实是根据匿名内部类中重写的 compare 方法作为排序机制和去重机制

                好的,多的也不瞎扯了,执行完 compare 方法,回到原来的 if 结构中:

                其实 该 if 结构中的代码本文在  "4. TreeSet源码解读:演示第一种排序机制:元素类实现Comparable接口" 部分已经非常详细的讲解过了,只有 cmp = cpr.compare(key,t.key) 这一条语句的差别,大家可以往前翻一下,这里就不再赘述了、

                继续往下执行,如下:

                "老演员"了

                首先,将添加的元素 3 Entry 包装起来;后面的 if--else 结构根据 cmp 值决定元素 3 存放在父节点 parent 的左结点还是右结点;接着,因为我们在红黑树中插入了一个新结点,所以可能需要进行红黑树结点颜色的调整、旋转等,因此调用 fixAfterInsertion(e) 函数,函数名称见名知意:"修复插入的问题"。最后就是更新集合的元素个数 size、更新集合修改次数 modCountreturn null 表示添加元素成功

                put 方法执行结束

                ② 逐层返回,跳回演示类中

                执行完 put 方法后,逐层返回,回到演示类,第二个元素 3 添加成功,查看此时的TreeSet 集合的状态,如下:

                可以看到,添加的元素 3 存放在元素 100 的右节点 right ,此时的红黑树结构如下所示:

        5.5 继续向集合添加元素

        继续向集合中添加元素 3(重复元素,放弃添加)、50、44,添加之后的 TreeSet 集合状态如下:

         可以看到,根节点 root 变成了元素 50 ,集合元素个数 size = 4,集合更改次数 modCount  = 4,此时的红黑树结构如下所示: 

        每个结点满足 right > root > left

        5.6 输出结果

         treeSet 集合输出结果如下:

        可以看到,System.out.print(treeSet) 输出的是 treeSet 集合的降序排序结果 

🆗,TreeSet 第二种排序机制的源码分析完毕,下面进行 TreeMap 的源码分析

6.TreeMap简介+底层实现

说明:TreeMap 的底层实现大多在上面的 TreeSet 部分基本已经讲完了

① TreeMap作为 Map 接口(双列集合)的一个实现类,也是保存和记录 key-value 的映射关系——键值对的,其类定义与继承关系图如下:

② TreeSet 集合一样,TreeMap 中的每个结点类型是 TreeMap$EntryEntryTreeMap 的一个静态内部类,其类的定义如下:

注意,对于 TreeSet 来说,每个结点的 value 值是 PRESENT占位符,无实际意义。而 TreeMap 中每个 value 值存储的是一个有意义的值,是 key-value 键值对中的 value 

② TreeMap 中,key 不可以为 null ,否则会报 NullPointerException ;但是 value 可以为 null

③ TreeMap 也可以对元素进行自定义排序,实现方式与 TreeSet 一致

  • 排序机制要求:

    • 要么元素类实现 Comparable 接口,重写 compareTo 方法,自定义排序逻辑

    • 要么在创建 TreeMap 集合时提供 Comparator 比较器并重写 compare 方法,自定义排序逻辑

不过需要注意的是,是根据 key-value 键值对中的 key 进行排序的,至于为什么是根据 key 排序,看上文 TreeSet 的源码解读就知道了,关键代码在 TreeMap 类中的 put 方法,如下:

 可以看到,两种排序机制比较的都是 key-value 键值对中的 key

TreeMap 集合中,如果先添加键值对 {key:1,value:"小明"} ,再添加键值对 {key:1,value:"蔡徐坤"} ,那么 key = 1 对应的 value 会被覆盖为 "蔡徐坤" 。本质上其实就是 TreeMap 集合不允许 key 重复,判断 key 重复的机制与 TreeSet 是一样的,如下:

        (1)如果使用 TreeMap 的无参构建创建 TreeMap 集合,并且添加的元素类已经实现了Comparable 接口的 compareTo 方法,则使用 compareTo 方法去重。即如果 compareTo 方法返回值为 0 ,则认为是相同的 key ,用新的 value 覆盖旧的 value 

        (2)如果使用 TreeMap 的有参构造创建 TreeMap 集合时提供了 Comparator 比较器,就使用重写的 compare 方法进行去重。即如果 compareTo 方法返回值为 0 ,则认为是相同的 key ,用新的 value 覆盖旧的 value 

实现代码其实在 TreeSet 源码解读部分讲解过了,关键代码在 TreeMap 类中的 put 方法,如下:

7.TreeMap源码解读

由于流程和 TreeSet 是基本一致的,所以此处就简单过一下,点到为止

        7.1 准备工作

        使用的案例中,键值对 key-value 类型是 Integer-String 。但不再使用 Integer 类实现的Comparable接口的 compareTo 方法,而是在创建 TreeMap 集合时传入一个 Comparator 比较器,在比较器中重写 compare 方法,作为 TreeMap排序机制和判断重复逻辑

        🆗,将用以下代码作为演示类

import java.util.Comparator;
import java.util.TreeMap;
public class demo {public static void main(String[] args) {TreeMap<Integer, String> treeMap = new TreeMap<>(new Comparator<Integer>() {@Overridepublic int compare(Integer x, Integer y) {//按key降序排序,若返回值为0,说明key重复return (x < y) ? 1 : ((x == y) ? 0 : -1);}});treeMap.put(23,"鸡你太美");treeMap.put(50,"Jack");treeMap.put(111,"小马");treeMap.put(111,"蔡徐坤");//key = 111 重复,用"蔡徐坤"覆盖"小马"System.out.println("按 key 进行降序排序,结果如下:");for (Integer key : treeMap.keySet()) {String value = treeMap.get(key);System.out.println(key + " = " + value);}}
}

        我们提供的 Comparator 比较器实现的是key 降序排序

        所以该红黑树的排序规则如下:

left > root > right

        判断重复逻辑:当前添加的 key-valuekey 与某个结点的 key 使用 Comparator 比较器中重写的 compare 方法比较时,如果返回值 = 0,则为重复,用新的 vaue 覆盖旧 value

        7.2 TreeMap构造器

        同样地,先跳入 TreeMap 的构造器看看,如下 : 

        还是那老一套,将实现了 Comparator 接口的匿名内部类对象传递给 TreeMapcomparator属性

        7.3 向集合添加第一个元素

        向集合添加第一个元素 {key:23,value:"鸡你太美"} ,跳入 put 方法,如下 : 

        可以看到,此时的 value 值已经不是PRESENT占位符,而是传入的实参 "鸡你太美"

        由于是第一个添加的元素,所以此时 t = root = null,因此会执行 if 结构中的代码。所以说和 TreeSet 基本就一套,没啥讲得😂

        7.4 向集合添加第二个元素

        向集合添加第二个元素 {key:50,value:"Jack"} ,仍然是跳入 put 方法,我们直接定位到关键部分,如下:

          这时候,我们跳入 compare 方法,根据动态绑定机制,一定会跳到演示类中的匿名内部类中,如下图所示 : 

        OK,接下来还是会根据 compare 方法的返回值进行判断,执行对应的添加操作。这里就不再演示了,我们将剩余的元素 {key:111,value:"小马"} 、{key:111,value:"蔡徐坤"} (key重复)添加进去,如下图所示 :  

         可以看到, 3 个键值对成功添加到 TreeMap 集合中,元素个数 size= 3,修改集合次数modCount = 3(在 key 重复时,用新 value 值覆盖旧 value 值,modCount 不更新)

        7.5 输出结果

       TreeMap 集合输出结果如下:

        可以看到,TreeMap 集合输出的结果确实是按 key 值降序排序的,而且后添加的键值对{key:111,"蔡徐坤"} 的 "蔡徐坤" 确实覆盖了旧键值对 {key:111,"小马"} 的 "小马" 

8.完结

🆗,以上就是 TreeSetTreeMap 的源码解读了,其实就是把 TreeMap 讲了两遍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/75403.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++初学】C++核心编程技术详解(二):类与继承

函数提高 3.1 函数默认参数 函数的形参可以有默认值&#xff0c;调用时可以省略这些参数。 示例代码&#xff1a; int func(int a, int b 10, int c 10) {return a b c; }int main() {cout << "ret " << func(20, 20) << endl;cout <<…

Next.js + Droplet:高并发视频内容平台部署与优化扩展实战

在构建在线服务时&#xff0c;无论你是开发者还是企业技术负责人&#xff0c;扩展性和稳定性始终是绕不开的核心挑战。尤其在涉及高并发访问、大量数据传输和持续内容分发的场景中&#xff0c;系统架构的设计直接决定了用户体验与业务成效。 本文将以视频点播&#xff08;Video…

Python爬虫第13节-解析库pyquery 的使用

目录 前言 一、pyquery 初始化 1.1 字符串初始化 1.2 URL 初始化 1.3 文件初始化 二、基本 CSS 选择器 三、pyquery 查找节点 3.1 子节点 3.2 父节点 3.3 兄弟节点 四、遍历 五、获取信息 5.1 获取属性 5.2 获取文本 六、节点操作 6.1 addClass 和 removeClass…

【Hadoop入门】Hadoop生态之Pig简介

1 什么是Pig&#xff1f; 在大数据分析领域&#xff0c;Apache Pig是一个不可忽视的重要工具。Pig是Apache Hadoop生态系统中的一个高级数据分析平台&#xff0c;它提供了一种称为Pig Latin的脚本语言&#xff0c;用于简化大规模数据集的并行处理。Pig的核心思想是将复杂的数据…

python 语言 设计模式

python 语言 设计模式 设计模式是指在软件开发过程中,针对反复出现的问题所总结归纳出的通用解决方案。以下是一些常见的Python语言设计模式: 目录 python 语言 设计模式创建型模式结构型模式行为型模式创建型模式 单例模式 定义:保证一个类只有一个实例,并提供一个全局访…

QT QML布局

一、‌锚点布局(Anchors) 通过定义元素与其他元素或父容器的锚点关系实现定位,支持动态调整和边距控制‌。 Rectangle {anchors.left: parent.left // 左对齐父容器anchors.top: parent.top // 顶部对齐父容器anchors.margins: 10 // 统一设置四周边距width: …

【力扣03】无重复字符的最长子串

题目 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc"&#xff0c;所以其长度为 3。示例 2: 输入: s "bbbbb" 输出: 1 解释:…

一文介绍阿里32B推理模型

什么是QwQ-32B&#xff1f; QwQ-32B并非普通的聊天机器人模型&#xff0c;而是推理模型。推理模型专注于逻辑拆解问题、分步推导&#xff0c;并输出结构化答案。 通过下面的示例&#xff0c;我们可以直观看到QwQ-32B的思考过程&#xff1a; qwq-32b思考过程 如果你需要写作辅…

AutoGen深度解析:从核心架构到多智能体协作的完整指南

AutoGen是微软推出的一个革命性多智能体(Multi-Agent)框架&#xff0c;它通过模块化设计和灵活的对话机制&#xff0c;极大地简化了基于大型语言模型(LLM)的智能体系统开发。本文将深入剖析AutoGen的两个核心模块——core基础架构和agentchat多智能体对话系统&#xff0c;带您全…

HTML的svg元素

<svg>元素 <svg>是一种用于描述二维矢量图形的 XML 格式&#xff0c;可以直接嵌入 HTML 文档中。 <svg>基本用法 <svg>的几种基本用法,包括圆形&#xff0c;正方形&#xff0c;三角形&#xff0c;直线 &#xff0c;折线等 <body><svg widt…

Qt 子项目依赖管理:从原理到实践的最佳分析:depends还是 CONFIG += ordered

1. 问题背景 在Qt项目开发中&#xff0c;当一个工程包含多个子项目&#xff08;如库、插件、测试模块&#xff09;时&#xff0c;如何正确管理它们的构建顺序和依赖关系&#xff1f; 如&#xff1a; 在开发一个包含核心库&#xff08;core&#xff09;、GUI模块&#xff08;g…

业务幂等性技术架构体系-接口幂等

接口幂等 对于幂等的考虑&#xff0c;主要解决两点前后端交互与服务间交互。这两点有时都要考虑幂等性的实现。从前端的思路解决 的话&#xff0c;主要有三种&#xff1a;前端防重、PRG模式、Token机制。 前端防重 通过前端防重保证幂等是最简单的实现方式&#xff0c;前端相关…

AI工具导航大全 | 2025精选版(持续更新)

&#x1f680; AI工具导航大全 | 2025精选版&#xff08;持续更新&#xff09; 更新日期&#xff1a;2025-04-11 | 适用场景&#xff1a;学术研究 | 办公提效 | 创意设计 | 开发编程 数据来源&#xff1a;综合高校实验室、企业实践及开发者社区推荐 &#x1f50d; 导航目录 &…

驱动-内核空间和用户空间数据交换

内核空间与用户控件数据交换 前面了解的字符设备中对 file_operations 结构体的进行了填充&#xff0c; 该 结构体的每一个成员都对应着一个系统调用&#xff0c; 例如 read、 write 等&#xff0c; 在字符设备相关的文章中有实验过对 调用函数进行了标志打印&#xff0c; 并没…

5G_WiFi_CE_DFS

目录 一、规范要求 1、法规目录 2、定义 3、运行模式 4、主/从设备相关的运行行为及具体的动态频率选择&#xff08;DFS&#xff09;要求 5、产品角色确定测试项目 6、测试项目 测试项1&#xff1a;信道可用性检查&#xff08;Channel Availability Check&#xff09; …

Devops之GitOps:什么是Gitops,以及它有什么优势

GitOps 定义 GitOps 是一种基于版本控制系统&#xff08;如 Git&#xff09;的运维实践&#xff0c;将 Git 作为基础设施和应用程序的唯一事实来源。通过声明式配置&#xff0c;系统自动同步 Git 仓库中的期望状态到实际运行环境&#xff0c;实现持续交付和自动化运维。其核心…

【蓝桥杯】单片机设计与开发,第十二届

/*头文件声明区*/ #include <STC15F2K60S2.H>//单片机寄存器头文件 #include <init.h>//初始化底层驱动头文件 #include <led.h>//led,蜂鸣器,继电器底层驱动头文件 #include <key.h>//按键底层驱动头文件 #include <seg.h>//数码管底层驱动头…

Vue3连接MQTT作为客户端

先下载依赖 npx --yes --registry https://registry.npmmirror.com npm install mqtt 在src的api创建 mes.js // 导入axios import axios from axios;// 定义一个变量,记录公共的前缀, baseURL const baseURL http://localhost:8080; const instance axios.create({ base…

主服务器和子服务器之间通过NFS实现文件夹共享

背景&#xff1a; 子服务器想做一个备份服务器 但是之前有很多文件是上传到本地的&#xff0c;于是服务要从本地读取文件 但是在不在同一台服务器中&#xff0c;读取就会有问题&#xff0c;想 实现在两者之间创建一个共享文件夹 一 NFS挂载步骤&#xff1a; 在主服务器&#…

LeetCode算法题(Go语言实现)_39

题目 给定一个二叉树的根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 一、代码实现 type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode }func rightSideView(root *TreeNode) []int {i…