Java集合之TreeMap源码解析上篇

上期回顾

上期我从树型结构谈到了红黑树的概念以及自平衡的各种变化指路上期←戳,本期我将会对TreeMap结合红黑树理论进行解读。

首先,我们先来回忆一下红黑树的5条基本规则。

1.结点是红色或者黑色,

2.根结点为黑色,

3.每个叶子结点都是黑色的空结点,

4.每个红色结点的两个子结点都是黑色,

5.从任何一个结点到叶子结点的所有路径的黑色结点的个数相同。

 

TreeMap的基本结构

我们先来看一下TreeMap的成员属性,

1     private final Comparator<? super K> comparator;
2     private transient Entry<K,V> root;
3     private transient int size = 0;
4     private transient int modCount = 0;

第1行,比较器,主要是用来维持树结构的有序,可以为空,如果为空,那按照key的自然规则进行排序,

第2行,Entry的引用root,是树型结构的根,

第3行,记录map中数据元素的个数,

第4行,记录map中数据更新的次数,

 

Entry是TreeMap的一个静态内部类,一个Entry对应一个树型结构中的结点,下面我们来看一下Entry的成员属性和构造函数,

 1  static final class Entry<K,V> implements Map.Entry<K,V> {
 2         K key;
 3         V value;
 4         Entry<K,V> left;
 5         Entry<K,V> right;
 6         Entry<K,V> parent;
 7         boolean color = BLACK;
 8 
 9         Entry(K key, V value, Entry<K,V> parent) {
10             this.key = key;
11             this.value = value;
12             this.parent = parent;
13         }

第2行,键

第3行,值

第4行,左结点的引用

第5行,右结点的引用

第6行,父结点的引用

第7行,当前结点默认为黑色结点

第9行,构造函数。初始化键、值、父结点引用

 

红黑树中的第1条规则是结点是红色或者黑色,在TreeMap中声明了两个常量表示

1 private static final boolean RED   = false;
2 private static final boolean BLACK = true;

false表示红色,true表示黑色,根结点是黑色,因此是true。

 

TreeMap的put方法解读

下面我们解读一下map在新增元素时发生哪些动作?我们以一个public修饰的静态方法,返回值为void,参数为String数组的main方法

作为程序的切入口,如下一段代码,

1 public static void main(String[] args) {
2         TreeMap<Integer, Integer> map = new TreeMap<Integer, Integer>();
3         map.put(50,3001);
4         map.put(30,3002);
5         map.put(70,3003);
6 }

第2行,new TreeMap<Integer, Integer>(),是一个无参的构造函数,初始化比较器为null,

第3行,新增一个键为50,值为3001的元素,

我们来跟一下put方法,源代码如下,

 1 public V put(K key, V value) {
 2         Entry<K,V> t = root;
 3         if (t == null) {
 4             compare(key, key); // type (and possibly null) check
 5 
 6             root = new Entry<>(key, value, null);
 7             size = 1;
 8             modCount++;
 9             return null;
10         }
11         int cmp;
12         Entry<K,V> parent;
13         // split comparator and comparable paths
14         Comparator<? super K> cpr = comparator;
15         if (cpr != null) {
16             do {
17                 parent = t;
18                 cmp = cpr.compare(key, t.key);
19                 if (cmp < 0)
20                     t = t.left;
21                 else if (cmp > 0)
22                     t = t.right;
23                 else
24                     return t.setValue(value);
25             } while (t != null);
26         }
27         else {
28             if (key == null)
29                 throw new NullPointerException();
30             @SuppressWarnings("unchecked")
31                 Comparable<? super K> k = (Comparable<? super K>) key;
32             do {
33                 parent = t;
34                 cmp = k.compareTo(t.key);
35                 if (cmp < 0)
36                     t = t.left;
37                 else if (cmp > 0)
38                     t = t.right;
39                 else
40                     return t.setValue(value);
41             } while (t != null);
42         }
43         Entry<K,V> e = new Entry<>(key, value, parent);
44         if (cmp < 0)
45             parent.left = e;
46         else
47             parent.right = e;
48         fixAfterInsertion(e);
49         size++;
50         modCount++;
51         return null;
52 }

第2行,把变量t指向根结点的引用,

第3行,判断根结点是否为空,如果为空说明,当前数据元素时首次新增,

第4行,调用自定义的比较器或者自然比较器,自身与自身做比较,旨在判断key是否是null,如果为空,则抛出NPE,

因此,TreeMap中的Key不允许为空。

第6-9行,把root引用指向当前元素,并且树结构的数量size更新为1,modCount自增,结束,返回值为null,

 

第11行,声明一个int类型的cmp,用来记录比较器的返回值

第12行,声明一个Entry类型的parent,用来记录待存储元素的父结点。上面Entry的构造函数中,其中一个数据项就是父结点,

 

第15行-26行,当比较器不为空时,找出待存储元素的父结点,下面我们来逐行阅读

第18行,根据我们自定义的比较规则,待存储元素的键和当前结点比较大小,

第19行,如果小于当前结点的键,那么下一个比较的目标应在在左子树上,

第21行,如果大于当前结点的键,那么下一个比较的目标应该在右子树上,

第24行,如果待存储的键等于当前结点的键,那么将新值更新,同时返回旧值。

 

第27行-42行,当比较器为空时,采用默认的自然比较规则,找出父结点上述基本相似,

注意:第28行,对key是否为空校验,因此:TreeMap中的Key不允许为空。

 

第43行,赋值,

第44行-47行,确定新增元素时父结点的左结点还是右结点。

第49行-50行,size和modCount分别自增,

 

接下来我们着来分析第48行,fixAfterInsertion(e),跟一下代码,

 1 private void fixAfterInsertion(Entry<K,V> x) {
 2         x.color = RED;
 3 
 4         while (x != null && x != root && x.parent.color == RED) {
 5             if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
 6                 Entry<K,V> y = rightOf(parentOf(parentOf(x)));
 7                 if (colorOf(y) == RED) {
 8                     setColor(parentOf(x), BLACK);
 9                     setColor(y, BLACK);
10                     setColor(parentOf(parentOf(x)), RED);
11                     x = parentOf(parentOf(x));
12                 } else {
13                     if (x == rightOf(parentOf(x))) {
14                         x = parentOf(x);
15                         rotateLeft(x);
16                     }
17                     setColor(parentOf(x), BLACK);
18                     setColor(parentOf(parentOf(x)), RED);
19                     rotateRight(parentOf(parentOf(x)));
20                 }
21             } else {
22                 Entry<K,V> y = leftOf(parentOf(parentOf(x)));
23                 if (colorOf(y) == RED) {
24                     setColor(parentOf(x), BLACK);
25                     setColor(y, BLACK);
26                     setColor(parentOf(parentOf(x)), RED);
27                     x = parentOf(parentOf(x));
28                 } else {
29                     if (x == leftOf(parentOf(x))) {
30                         x = parentOf(x);
31                         rotateRight(x);
32                     }
33                     setColor(parentOf(x), BLACK);
34                     setColor(parentOf(parentOf(x)), RED);
35                     rotateLeft(parentOf(parentOf(x)));
36                 }
37             }
38         }
39         root.color = BLACK;
40 }

fixAfterInsertion,方法名直译过来就是插入之后做调整之意,
第2行,将新元素的结点置为红色,
第4行-38行,调整的过程。
第39行,将调整后的树结构的根置为黑色

下面我们着重来分析第4-38行代码,
第5行→x的父结点等于x的父结点的父结点的左结点 → x的父结点 是 x父结点的父结点的左结点,

 

以上3张图都符合,x的父结点是a,a的父结点是b,所以parentOf(x) == leftOf(parentOf(parentOf(x)))

用一句话来描述就是,x的父结点a是a的父结点的左孩子。

第6行,rightOf(parentOf(parentOf(x))),x的父结点的父结点的右结点,记作y,

第7行,如果y为红色,把y变成黑色,把a变成黑色,把b变为红色(如下图所示),同时把x引用指向x的父结点的父结点也就是b结点,本次循环结束,继续下一轮的循环调整。

第7行,如果y是黑色,

如果x是结点的右结点,左旋转a(第13行-15行)此时x的引用指向了a,

 

 第33行-35行,x(图中的a,因为x引用指向了a)的父结点(图中的x)变为黑色,x父结点(图中的a)的父结点变为红色,对x的父结点的父结点(图中的b)进行右旋

第7行,如果y是黑色,

如果x不是结点的右结点,左旋转a(第13行-15行)此时x的引用指向了a】,把x的父结点变为黑色,把x的父结点的父结点变为红色,最后将x的父结点的父结点右旋

当前分支结束。

 

下面我们重新回到第5行,

第5行→②x的父结点 不是 x父结点的父结点的左结点,换一句话说就是,

x的父结点 是 x父结点的父结点的右结点,和上述情况比较类似,我们来看一组示意图,

 

 

以上3张图都符合,x的父结点是a,a的父结点是b,所以parentOf(x) == rightOf(parentOf(parentOf(x)))

用一句话来描述就是,x的父结点a是a的父结点的右孩子。

第22行,leftOf(parentOf(parentOf(x))),x的父结点的父结点的左结点,记作y,

第23行,如果y为红色,把y变成黑色,把a变成黑色,把b变为红色(如下图所示),同时把x引用指向x的父结点的父结点也就是b结点,本次循环结束,继续下一轮的循环调整。

第23行,如果y是黑色,

如果x是结点的左结点,右旋转a(第29行-32行)此时x的引用指向了a,

第33行-35行,x(图中的a,因为x引用指向了a)的父结点(图中的x)变为黑色,x父结点(图中的a)的父结点变为红色,对x的父结点的父结点(图中的b)进行右旋

 当前分支结束,进入下一轮循环。

 

以上是对于TreeMap的put方法的解读,比较复杂。下面我简单总结一下fixAfterInsertion的过程

第1步,将新增的元素x置为红色,

第2步,判断新增元素x不为空,且不是root结点,且x的父结点是红色,如果满足,进入第3步,如果不满足跳到第16步,

第3步,判断x的父结点a是否是a的父结点的左结点,如果是,进入第4步,如果不是进入10步,

 

第4步,取出x父结点的父结点的右结点,记作y,

第5步,判断y的颜色是否为红色,如果是,进入第6步,如果不是进入7步,

第6步,x的父结点置为黑色,x父结点的父结点的右结点,即y置为黑色,x的父结点的父结点置为红色,x的引用指向x的父结点的父结点,跳到第2步,

 

第7步,判断x是否是x父结点的右结点,如果是,进入第8步,如果不是,进入第9步,

第8步,x的应用指向x的父结点,x左旋(注意,此时x指向的是原x的父结点)

第9步,x的父结点置为黑色,x的父结点的父结点置为红色,x的父结点的父结点右旋,跳到第2步,

 

第10步,取出x父结点的父结点的左结点,记作y,

第11步,判断y的颜色是否为红色,如果是,进入第12步,如果不是进入13步,

第12步,x的父结点置为黑色,x父结点的父结点的左结点,即y置为黑色,x的父结点的父结点置为红色,x的引用指向x的父结点的父结点,跳到第2步,

 

第13步,判断x是否是x父结点的左结点,如果是,进入第14步,如果不是,进入第15步,

第14步,x的引用指向x的父结点,x右旋(注意,此时x指向的是原x的父结点)

第15步,x的父结点置为黑色,x的父结点的父结点置为红色,x的父结点的父结点左旋,跳到第2步,

 

第16步,把根节点置为黑色。

 

 由于内容较多,因此TreeMap源码解析分为上篇、中篇和下篇3部分,

下期预告

1 TreeMap<Integer, Integer> map = new TreeMap<Integer, Integer>();
2          map.put(50,3001);
3          map.put(30,3002);
4          map.put(70,3003);

 

本文提到的简单例子的树我们还没有建立起来,由于内容较多,所以打算,把这一部分也放在【java集合之TreeMap源码解析中篇】来讲解。谢谢关注。

 

转载于:https://www.cnblogs.com/sunshine798798/p/9118905.html

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

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

相关文章

仪器和软件通讯测试软件,软件定义的仪器-测试测量-与非网

如同每个孩子所拥有的第一套LEGO玩具改变了他们对世界的认识一样&#xff0c;26年前&#xff0c;美国国家仪器通过NI LabVIEW系统设计软件&#xff0c;重新改变了人们对仪器的认知。今年&#xff0c;NI将再次重演历史&#xff0c;发布一款新型仪器&#xff0c;帮助测试工程师摆…

埃森哲杯第十六届上海大学程序设计联赛春季赛暨上海高校金马五校赛 C序列变换...

链接&#xff1a;https://www.nowcoder.com/acm/contest/91/C来源&#xff1a;牛客网没有账号的同学这样注册&#xff0c;支持博主 题目描述 给定两个长度为n的序列&#xff0c;ai, bi(1<i<n), 通过3种魔法使得序列a变换为序列b&#xff0c;也就是aibi(1<i<n). 魔…

Spring MVC,Ajax和JSON第1部分–设置场景

我一直在考虑在Spring&#xff0c;Ajax和JSON上写博客&#xff0c;但是我从来没有做过。 这主要是因为它非常复杂&#xff0c;并且所需的技术一直处于变化状态。 当我决定撰写此博客时&#xff0c;我在Internet上有一个侦察员&#xff0c;如果您查看诸如Stack Overflow之类的地…

柜员计算机技能,新入职柜员必备软件:柜员技能训练系统最新版

如果你是新入职柜员的大学生&#xff0c;这个软件你肯定用得着&#xff01;如果你是资格老的柜员同胞&#xff0c;这个软件你肯定用得着&#xff01;这个软件&#xff0c;针对柜员的小键盘、打字和点钞三项技能要求&#xff0c;专门针对痛点开发&#xff0c;可以有效训练柜员的…

Html5和Css3扁平化风格网页

前言 扁平化概念的核心意义 去除冗余、厚重和繁杂的装饰效果。而具体表现在去掉了多余的透视、纹理、渐变以及能做出3D效果的元素&#xff0c;这样可以让“信息”本身重新作为核心被凸显出来。同时在设计元素上&#xff0c;则强调了抽象、极简和符号化。 示例 视频效果&…

按功能而不是按层打包课程

大多数企业Java应用程序在设计上都有一些相似之处。 这些应用程序的打包通常由它们使用的框架&#xff08;如Spring&#xff0c;EJB或Hibernate等&#xff09;驱动。或者&#xff0c;您可以按功能对打包进行分组。 像其他任何有关建模的项目一样&#xff0c;这也不是没有任何问…

总是助手服务器失败怎么回事,《遇见逆水寒》连接服务器失败解决方法汇总 服务器连接失败问题原因...

导读遇见逆水寒连接服务器失败怎么回事&#xff0c;近期不少小伙伴都在反映遇见逆水寒助手连接服务器失败&#xff0c;一直登不上去是怎么回事&#xff0c;小编这就为大家分享下遇见逆水寒连接服务器失败解决方法。遇见逆水寒连接服务器失败解决方法...遇见逆水寒连接服务器失败…

Linux常用开发环境软件-redis安装

linux下安装redis3.2.11版本  1、安装编译环境 yum install gcc  //安装编译环境 2、到官网下载redis 官网地址&#xff1a;https://redis.io/download 3、用WinScp工具&#xff0c;将下载好的redis-3.2.11.tar.gz传输到linux服务器下的opt目录下(opt就相当于window的d://so…

项目第十一天

站立式会议&#xff1a; 燃尽图&#xff1a; 项目&#xff1a; 项目进展&#xff1a;系统完成&#xff0c;进行测试。 问题&#xff1a;测试的时候发现不知道如何进行系统的测试&#xff0c;所以测试内容的比较乱。 体会&#xff1a;从无到有完成一个项目&#xff0c;需要很多步…

JPA:确定关系的归属方

使用Java Persistence API&#xff08;JPA&#xff09;时&#xff0c;通常需要在两个实体之间创建关系。 这些关系是通过使用外键在数据模型&#xff08;例如数据库&#xff09;中定义的&#xff0c;而在我们的对象模型&#xff08;例如Java&#xff09;中则使用注释来定义关联…

服务器芯片镜像测试,模拟镜像服务器磁盘问题的两个测试【转】

我们知道在高安全模式下&#xff0c;在主服务器上提交的事务必须同时在镜像服务器上提交成功&#xff0c;否则该事务无法在主数据库上提交。在上面的图中&#xff0c;一个事务在主数据库上提交的步骤包含&#xff1a;客户端程序将事务发送给主数据库服务器SQLServer主数据库服务…

运用Arc Hydro提取河网

Arc hydro 插件需要 spatial analyst 支持&#xff1a; 解决方法&#xff1a;Tools菜单>>Extensions...&#xff0c;勾选Spatial Analyst 1.设置存储路径 ApUtilities-set target locations 2.导入dem 3.拼接dem Dataset Name 设置为.tif,即存为tif格式&#xff0c;否则…

服务器性能是什么,什么是服务器性能的显卡,怎么理解?

什么是服务器性能的显卡&#xff0c;怎么理解&#xff1f;如果单说“”二字就是个伪命题&#xff0c;服务分不同的性能级别&#xff0c;有些刀片器的性能甚至还不如某些发烧级的游戏台式机&#xff0c;那它所用的显卡性能也会比较一般&#xff0c;如果是说哪些显卡是专门为服务…

打印机网络共享服务器不稳定,共享打印机无法访问怎么办,教你一招问题立马解决...

基本上只有本地打印机共享才会出现下面的问题&#xff0c;通过网线连接和打印机服务器连接的打印机不会出现此类问题&#xff0c;多个电脑连接网络打印机本质上还是各个电脑连接的本地打印机。一、 设置打印机共享时出现错误连接好本地打印机后&#xff0c;想要共享给同事使用&…

CSS margin 外边距 属性的位置关系

padding&#xff1a;内边距margin &#xff1a;外边距 margin:10px; 所有 4 个外边距都是 10px ******************************************* margin:10px 5px; 上外边距和下外边距是 10px右外边距和左外边距是 5px ******************************************** margin:10px…

C语言博客作业--字符数组

一、PTA实验作业 题目1&#xff1a;7-1 字符串转换成十进制整数 1. 本题PTA提交列表 2. 设计思路 3.代码截图 4.本题调试过程碰到问题及PTA提交列表情况说明。 这个问题我看了好久&#xff0c;试了很多的的错误点&#xff0c;答案和我预料的一样&#xff0c;所以我重新看了一遍…

Java 7 Update 21安全改进的详细信息

甲骨文昨天发布了三个Java更新 。 重要的是要注意它们包含一些与安全性相关的更改。 一段时间以来&#xff0c;已经宣布了其中的大多数更改&#xff0c;并且首先要注意的是Oracle按计划交付。 甲骨文公司Java平台安全经理Milton Smith最近在DevoxxUK上做了题为“ 用Java保护…

du的原理 linux_Linux 文件系统管理

1、文件系统介绍1&#xff09;、Linux 文件系统特性super block&#xff08;超级块&#xff09;记录整个文件系统的信息。包括 block 与 inode 的总量&#xff0c;已经使用的 block 和 inode 的数量&#xff0c;未使用的 block 和 inode 的数量&#xff0c;block 与 inode 的大…

box-shadow IE8兼容处理

根据canisue&#xff08;http://caniuse.com/#searchbox-shadow&#xff09;&#xff0c;box-shadow兼容性如下图所示&#xff1a; 测试代码&#xff1a; 1 <!DOCTYPE html>2 <html>3 4 <head>5 <meta charset"UTF-8">6 …

ECharts.js学习(一) 简单入门

EChart.js 简单入门 最近有一个统计的项目要做&#xff0c;在前端的数据需要用图表的形式展示。网上搜索了一下&#xff0c;发现有几种统计图库。 MSChart 这个是Visual Studio里的自带控件&#xff0c;使用比较简单&#xff0c;不过数据这块需要在后台绑定。 ichartjs 是一款…