HashMap中的TreeNode,红黑树源码分析

在看HashMap的源码时候看到了TreeNode。因此需要对其进行一个了解。是一个红黑树。可以百度一下红黑树的数据结构。分析了下源码,还是比较枯燥的

红黑树的性质:本身是一个二叉查找树(所有左节点的值都比右节点的小)。另:

  1. 节点是红色或者黑色
  2. 根节点是黑色
  3. 每个叶节点(Nil节点,空节点)是黑色的
  4. 每个红节点对应的两个子节点都是黑色的(不可能有两个相连的红节点)。
  5. 从任意节点出发,到每个叶子节点都有相同的黑色节点。

这保证了红黑数是平衡的,从根到叶子的最长的可能路径不多于最短的可能路径的两倍长,因此插入、删除查找的最坏情况是有所保证的,与树的高度成正比。原因有两点:

  1. 最短的可能路径都是黑色节点。
  2. 最长的路径是红黑节点交替的路径。

因为从同一节点出发,到每一个叶子有相同的黑色节点,所以保证了最长路径是最短路径的两杯长。

一、成员变量与构造函数

 1  static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
 2         //父节点
 3         HashMap.TreeNode<K,V> parent;  // red-black tree links
 4         //左节点
 5         HashMap.TreeNode<K,V> left;
 6         //右节点
 7         HashMap.TreeNode<K,V> right;
 8         //用来删除下一个节点用的,因此prev也就是上一个节点
 9         HashMap.TreeNode<K,V> prev;    // needed to unlink next upon deletion
10         //节点是否为红色
11         boolean red;
12         TreeNode(int hash, K key, V val, HashMap.Node<K,V> next) {
13             super(hash, key, val, next);
14         }
15 }

二、Function root

 1 /**
 2          * 返回包含此节点的树的根节点
 3          */
 4         final HashMap.TreeNode<K,V> root() {
 5             //定义两个TreeNode,一个是父节点指针,指向当前节点的parent,所以功能很明显了。就是遍历直到头节点
 6             for (HashMap.TreeNode<K,V> r = this, p;;) {
 7                 if ((p = r.parent) == null)
 8                     return r;
 9                 r = p;
10             }
11         }

三、Function checkInvariants

 1 /**
 2          * 不变性检查,保证红黑树的结构不改变。
 3          */
 4         static <K, V> boolean checkInvariants(HashMap.TreeNode<K, V> t) {
 5             HashMap.TreeNode<K, V> tp = t.parent, tl = t.left, tr = t.right,
 6                     tb = t.prev, tn = (HashMap.TreeNode<K, V>) t.next;
 7             if (tb != null && tb.next != t)
 8                 return false;
 9             if (tn != null && tn.prev != t)
10                 return false;
11             if (tp != null && t != tp.left && t != tp.right)
12                 return false;
13             if (tl != null && (tl.parent != t || tl.hash > t.hash))
14                 return false;
15             if (tr != null && (tr.parent != t || tr.hash < t.hash))
16                 return false;
17             if (t.red && tl != null && tl.red && tr != null && tr.red)
18                 return false;
19             if (tl != null && !checkInvariants(tl))
20                 return false;
21             if (tr != null && !checkInvariants(tr))
22                 return false;
23             return true;
24         }
25     }

四、Function moveRootToFront

 1 /**
 2          * 确保所给的root是第一个节点。也就是把所给的root移到第一个节点。确保桶的红黑树的跟节点是root
 3          */
 4         static <K, V> void moveRootToFront(HashMap.Node<K, V>[] tab, HashMap.TreeNode<K, V> root) {
 5             int n;
 6             if (root != null && tab != null && (n = tab.length) > 0) {
 7                 //根节点的位置
 8                 int index = (n - 1) & root.hash;
 9
10                 //链表的操作,把root移到第一个节点。root的next指向原先的头节点,原先的头节点的prev指向root;
11                 //root的next的prev指向root的prev,root的prev指向root的next,即把root在prev和next中去掉
12                 HashMap.TreeNode<K, V> first = (HashMap.TreeNode<K, V>) tab[index];
13                 if (root != first) {
14                     HashMap.Node<K, V> rn;
15                     tab[index] = root;
16                     HashMap.TreeNode<K, V> rp = root.prev;
17                     if ((rn = root.next) != null)
18                         ((HashMap.TreeNode<K, V>) rn).prev = rp;
19                     if (rp != null)
20                         rp.next = rn;
21                     if (first != null)
22                         first.prev = root;
23                     root.next = first;
24                     root.prev = null;
25                 }
26                 //红黑树的一致性检查
27                 assert checkInvariants(root);
28             }
29         }

五、Function find

 1 /**
 2          * Finds the node starting at root p with the given hash and key.
 3          * The kc argument caches comparableClassFor(key) upon first use
 4          * comparing keys.
 5          */
 6         final HashMap.TreeNode<K, V> find(int h, Object k, Class<?> kc) {
 7             HashMap.TreeNode<K, V> p = this;
 8             do {
 9                 int ph, dir;
10                 K pk;
11                 HashMap.TreeNode<K, V> pl = p.left, pr = p.right, q;
12
13                 //p的hash > 目标hash, 则查找左子树,否则右子树
14                 if ((ph = p.hash) > h)
15                     p = pl;
16                 else if (ph < h)
17                     p = pr;
18                 //找到则返回
19                 else if ((pk = p.key) == k || (k != null && k.equals(pk)))
20                     return p;
21                 //如果左节点是Null则找右子树,右节点是Null则找左子树
22                 else if (pl == null)
23                     p = pr;
24                 else if (pr == null)
25                     p = pl;
26                 //如果不按照hash比较,则按照比较器比较,查找左子树还是右子树
27                 else if ((kc != null ||
28                         (kc = comparableClassFor(k)) != null) &&
29                         (dir = compareComparables(kc, k, pk)) != 0)
30                     p = (dir < 0) ? pl : pr;
31                 //如果在右子树找到则直接返回
32                 else if ((q = pr.find(h, k, kc)) != null)
33                     return q;
34                 //否则在左子树查找
35                 else
36                     p = pl;
37             } while (p != null);
38             //否则返回Null
39             return null;
40         }

六、Function tieBreakOrder

 1 /**
 2          * 在像红黑树插入即节点的时候,为了确定相同hashCode的节点插入的顺序,
 3          * 设定了插入顺序的规则,结果一定是不想等的。非左即右。
 4          */
 5         static int tieBreakOrder(Object a, Object b) {
 6             int d;
 7
 8             //会对两个类名相等的类进行比较
 9             if (a == null || b == null ||
10                     (d = a.getClass().getName().
11                             compareTo(b.getClass().getName())) == 0)
12                 //返回两个类内存地址的hashCode比较结果,小的或者相都是-1,否则1,并非是类的hashCode的比较
13                 d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
14                         -1 : 1);
15             return d;
16         }

七、Function rotateLeft, 左旋

图片来源(https://blog.csdn.net/sun_tttt/article/details/65445754)

 1 static <K, V> HashMap.TreeNode<K, V> rotateLeft(HashMap.TreeNode<K, V> root,
 2                                                         HashMap.TreeNode<K, V> p) {
 3             //三个节点,右节点,parent的parent节点,右的左节点
 4             HashMap.TreeNode<K, V> r, pp, rl;
 5             //该节点不为null并且右节点不为null
 6             if (p != null && (r = p.right) != null) {
 7                 //因为是左旋,所以如果右节点的左节点如果不为null,则rl的根节点设为p
 8                 if ((rl = p.right = r.left) != null)
 9                     rl.parent = p;
10                 //如果左旋后的头节点为根节点,则根据红黑树的性质,颜色为黑色
11                 if ((pp = r.parent = p.parent) == null)
12                     (root = r).red = false;
13                 //因为是左旋,所以p的位置由pr取代,所以p的parent节点的p位置设为现在pr的位置。
14                 else if (pp.left == p)
15                     pp.left = r;
16                 else
17                     pp.right = r;
18                 //然后r的left是p,p的父节点是r
19                 r.left = p;
20                 p.parent = r;
21             }
22             return root;
23         }

八、Function rightRotate, 右旋

图片来源(https://blog.csdn.net/sun_tttt/article/details/65445754)

 1 static <K, V> HashMap.TreeNode<K, V> rotateRight(HashMap.TreeNode<K, V> root,
 2                                                          HashMap.TreeNode<K, V> p) {
 3             //定义3个节点,左节点,头节点的头节点,左节点的右节点
 4             HashMap.TreeNode<K, V> l, pp, lr;
 5             //要旋转的节点和左节点不为空时
 6             if (p != null && (l = p.left) != null) {
 7                 //根据右旋,(原)头节点的左节点为原先左节点的右节点,并且把其父节点设为原头节点,即p
 8                 if ((lr = p.left = l.right) != null)
 9                     lr.parent = p;
10                 //同样,如果现在的头节点为根节点的话,标记节点的颜色为黑色
11                 if ((pp = l.parent = p.parent) == null)
12                     (root = l).red = false;
13                     //头节点的头节点设定其自节点
14                 else if (pp.right == p)
15                     pp.right = l;
16                 else
17                     pp.left = l;
18                 //同样,根据右旋,指定现在的头节点的右节点为原先的头节点,原先的头节点的父节点为现在的头节点
19                 l.right = p;
20                 p.parent = l;
21             }
22             return root;
23         }

九、Function balanceInsertion   代码和图结合看很好理解过程,至于为什么会平衡还要分析。

图片来源https://blog.csdn.net/weixin_42340670/article/details/80550932

1.无旋转

2.旋转情况1

3.旋转情况2

 1 /*
 2         保证插入节点后,红黑树仍然是平衡的,代码很长,结合图看更好理解点,这样看太抽象了。但是虽然逻辑复杂,但是足以
 3         见证红黑树的高效性,因为更新树的话只是旋转操作,即改变下指针的位置,并且设置一下节点的位置就可以了
 4          */
 5         static <K, V> HashMap.TreeNode<K, V> balanceInsertion(HashMap.TreeNode<K, V> root,
 6                                                               HashMap.TreeNode<K, V> x) {
 7             //先把节点设为红色
 8             x.red = true;
 9             //定义四个节点
10             for (HashMap.TreeNode<K, V> xp, xpp, xppl, xppr; ; ) {
11                 //如果x是根节点,则把它设为黑色,并返回根节点
12                 if ((xp = x.parent) == null) {
13                     x.red = false;
14                     return x;
15                 }
16                 //如果x的父节点即xp是黑色,并且xp为根节点,则返回,什么也不做。
17                 else if (!xp.red || (xpp = xp.parent) == null)
18                     return root;
19                 //如果xp为xp父节点的左节点
20                 if (xp == (xppl = xpp.left)) {
21                     //如果xpp的右节点非空并且是红色的,那么把其设为黑色,xpp的左节点也设为黑色,xpp设为红色,并且x等于xpp
22                     if ((xppr = xpp.right) != null && xppr.red) {
23                         xppr.red = false;
24                         xp.red = false;
25                         xpp.red = true;
26                         x = xpp;
27                     }
28                     //如果xpp的右节点是空或者为黑色的话
29                     else {
30                         //如果x是xp的右节点,那么左旋xp节点,并且重新更新xp和xpp
31                         if (x == xp.right) {
32                             root = rotateLeft(root, x = xp);
33                             xpp = (xp = x.parent) == null ? null : xp.parent;
34                         }
35                         //如果x的父节点不为空,先把它设为黑色
36                         if (xp != null) {
37                             xp.red = false;
38                             //如果xp的父节点不为空,则先把xpp设为红色,然后再右旋
39                             if (xpp != null) {
40                                 xpp.red = true;
41                                 root = rotateRight(root, xpp);
42                             }
43                         }
44                     }
45                 }
46                 //如果xp为xp父节点的右右节点
47                 else {
48                     //如果xpp的左节点非空并且是红色的话,把xppl设为黑色,xp设为黑色,xp的父节点设为红色
49                     if (xppl != null && xppl.red) {
50                         xppl.red = false;
51                         xp.red = false;
52                         xpp.red = true;
53                         x = xpp;
54                     }
55                     //如果xpp的左节点是空或者是黑色的话
56                     else {
57                         //如果x为父节点的左节点,则右旋xp节点,并重新设置xp,xpp
58                         if (x == xp.left) {
59                             root = rotateRight(root, x = xp);
60                             xpp = (xp = x.parent) == null ? null : xp.parent;
61                         }
62                         //如果x的父节点为空,
63                         if (xp != null) {
64                             //先把其设为黑色
65                             xp.red = false;
66                             //如果xp的父节点不为空,则xpp设为红色,并左旋xpp节点
67                             if (xpp != null) {
68                                 xpp.red = true;
69                                 root = rotateLeft(root, xpp);
70                             }
71                         }
72                     }
73                 }
74             }
75         }

十、Function treeify

 1 /**
 2          * 把链表生成红黑树,返回头节点
 3          */
 4         final void treeify(HashMap.Node<K, V>[] tab) {
 5             HashMap.TreeNode<K, V> root = null;
 6
 7             //两个指针,一个是链表的表头,一个是下一个指针
 8             for (HashMap.TreeNode<K, V> x = this, next; x != null; x = next) {
 9                 next = (HashMap.TreeNode<K, V>) x.next;
10                 x.left = x.right = null;
11
12                 //先设定 root为头节点,parent为null,根节点为黑色,
13                 if (root == null) {
14                     x.parent = null;
15                     x.red = false;
16                     root = x;
17                 } else {
18                     K k = x.key;
19                     int h = x.hash;
20                     Class<?> kc = null;
21
22                     //遍历红黑树
23                     for (HashMap.TreeNode<K, V> p = root; ; ) {
24                         int dir, ph;
25                         K pk = p.key;
26                         //如果当前树节点的hash > 链表节点的hash则dir值为-1
27                         if ((ph = p.hash) > h)
28                             dir = -1;
29                             //否则为1
30                         else if (ph < h)
31                             dir = 1;
32                             //如果不按照hash值比较的话,并且比较器不存在或者比较器比较的值是0的话,则把死结打开
33                         else if ((kc == null &&
34                                 (kc = comparableClassFor(k)) == null) ||
35                                 (dir = compareComparables(kc, k, pk)) == 0)
36                             dir = tieBreakOrder(k, pk);
37                         //设置一个红黑树的节点
38                         HashMap.TreeNode<K, V> xp = p;
39                         //设置节点的走向,如果dir <= 0则p为做节点,否则为右,也就是找到链表节点应该插入的位置
40                         if ((p = (dir <= 0) ? p.left : p.right) == null) {
41                             //设置链表节点的父节点
42                             x.parent = xp;
43                             if (dir <= 0)
44                                 xp.left = x;
45                             else
46                                 xp.right = x;
47                             //插入节点,并且不破坏红黑树的性质
48                             root = balanceInsertion(root, x);
49                             break;
50                         }
51                     }
52                 }
53             }
54             //设置头节点
55             moveRootToFront(tab, root);
56         }

原文地址:https://www.cnblogs.com/ylxn/p/10109913.html

时间: 2024-12-08 11:52:08

HashMap中的TreeNode,红黑树源码分析的相关文章

HashMap实现原理(jdk1.7),源码分析

HashMap实现原理(jdk1.7),源码分析 ? HashMap是一个用来存储Key-Value键值对的集合,每一个键值对都是一个Entry对象,这些Entry被以某种方式分散在一个数组中,这个数组就是HashMap的主干. 一.几大常量 //默认容量 16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //最大容量 static final int MAXIMUM_CAPACITY = 1 << 30; //默认负载因

caffe中HingeLossLayer层原理以及源码分析

输入: bottom[0]: NxKx1x1维,N为样本个数,K为类别数.是预测值. bottom[1]: Nx1x1x1维, N为样本个数,类别为K时,每个元素的取值范围为[0,1,2,-,K-1].是groundTruth. 输出: top[0]: 1x1x1x1维, 求得是hingeLoss. 关于HingeLoss: p: 范数,默认是L1范数,可以在配置中设置为L1或者L2范数. :指示函数,如果第n个样本的真实label为k,则为,否则为-1. tnk: bottom[0]中第n个样

Android 中View的绘制机制源码分析 三

到目前为止,measure过程已经讲解完了,今天开始我们就来学习layout过程,不过在学习layout过程之前,大家有没有发现我换了编辑器,哈哈,终于下定决心从Html编辑器切换为markdown编辑器,这里之所以使用"下定决心"这个词,是因为毕竟Html编辑器使用好几年了,很多习惯都已经养成了,要改变多年的习惯确实不易,相信这也是还有很多人坚持使用Html编辑器的原因.这也反应了一个现象,当人对某一事物非常熟悉时,一旦出现了新的事物想取代老的事物时,人们都有一种抵触的情绪,做技术的

OpenSSL 中 RSA 加密解密实现源码分析

1.RSA 公钥和私钥的组成,以及加密和解密的公式: 2.模指数运算: 先做指数运算,再做模运算,如 5^3 mod 7 = 125 mod 7 = 6 3.RSA加密算法流程: 选择一对不同的.并且足够大的素数 p 和 q 计算 n = p * q 计算欧拉函数 f(n) = (p-1) * (q-1),p 和 q 需要保密 寻找与 f(n) 互质的数 e,并且 1 < e < f(n) 计算 d,使得 d * e ≡ 1 mod f(n) 公钥 KU = (e , n)   私钥 KR =

Android 中View的绘制机制源码分析 二

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/46842891 本篇文章接着上篇文章的内容来继续讨论View的绘制机制,上篇文章中我们主要讲解了View的measure过程,今天我们就来学习ViewGroup的measure过程,由于ViewGroup只是一个抽象类,所以我们需要以一个具体的布局来分析measure过程,正如我上篇文章说的,我打算使用LinearLayout为例讲解measure过程,如果你还没有读过上篇文章,那么建议你先

Java中HashMap底层实现原理(JDK1.8)源码分析

这几天学习了HashMap的底层实现,但是发现好几个版本的,代码不一,而且看了Android包的HashMap和JDK中的HashMap的也不是一样,原来他们没有指定JDK版本,很多文章都是旧版本JDK1.6.JDK1.7的.现在我来分析一哈最新的JDK1.8的HashMap及性能优化. 在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效

HashMap和ConcurrentHashMap实现原理及源码分析

ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现,ConcurrentHashMap在并发编程的场景中使用频率非常之高,本文就来分析下ConcurrentHashMap的实现原理,并对其实现原理进行分析(JDK1.7). ConcurrentHashMap实现原理 众所周知,哈希表是中非常高效,复杂度为O(1)的数据结构,在Java开发中,我们最常见到最频繁使用的就是HashMap和HashTable,但是在线程竞争激烈的并发场景中使用都不够合理.

Android中AsyncTask基本用法与源码分析(API 23)

原文链接 http://sparkyuan.github.io/2016/03/23/AsyncTask源码剖析(API 23)/ 转载请注明出处 Android的UI是线程不安全的,想在子线程中更新UI就必须使用Android的异步操作机制,直接在主线程中更新UI会导致程序崩溃. Android的异步操作主要有两种,AsyncTask和Handler.AsyncTask是一个轻量的异步类,简单.可控.本文主要结合API 23的源码讲解一下AsyncTask到底是什么. 基本用法 声明:Andr

JDK中ArrayList、HashMap和HashSet的equals方法源码分析

最近遇到个坑,在分别对ArrayList.HashMap等数据类型进行比较时,发现数据一样,但equals一直返回false.于是乎看了一下ArrayList和HashMap的源码,才恍然大悟.本文的代码摘自JDK 1.7.0. ArrayList的equals方法: public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof List)) return false; ListIterator<