红黑树

红黑树是一种特殊的二叉树,主要用它存储有序的数据,提供高效的数据检索,时间复杂度为O(lgn),每个节点都有一个标识位表示颜色,红色或黑色,有如下5种特性:
1、每个节点要么红色,要么是黑色;
2、根节点一定是黑色的;
3、每个空叶子节点必须是黑色的;
4、如果一个节点是红色的,那么它的子节点必须是黑色的;
5、从一个节点到该节点的子孙节点的所有路径包含相同个数的黑色节点;

注:首先它是二叉树,所以还是要满足:左节点hash值<父节点<右节点

结构示意图

只要满足以上5个特性的二叉树都是红黑树,当有新的节点加入时,有可能会破坏其中一些特性,需要通过左旋或右旋操作调整树结构,重新着色,使之重新满足所有特性。

ConcurrentHashMap红黑树实现

在1.8的实现中,当一个链表中的元素达到8个时,会调用treeifyBin()方法把链表结构转化成红黑树结构,实现如下:

 /**
* Replaces all linked nodes in bin at given index unless table is
* too small, in which case resizes instead.
*/
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
synchronized (b) {
if (tabAt(tab, index) == b) {
TreeNode<K,V> hd = null, tl = null;
for (Node<K,V> e = b; e != null; e = e.next) {
TreeNode<K,V> p =
new TreeNode<K,V>(e.hash, e.key, e.val,
null, null);
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}

从上述实现可以看出:并非一开始就创建红黑树结构,如果当前Node数组长度小于阈值MIN_TREEIFY_CAPACITY,默认为64,先通过扩大数组容量为原来的两倍以缓解单个链表元素过大的性能问题。

红黑树构造过程

下面对红黑树的构造过程进行分析:
1、通过遍历Node链表,生成对应的TreeNode链表,其中TreeNode在实现上继承了Node类;

 class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
// needed to unlink next upon deletion
boolean red;
}

假设TreeNode链表如下,其中节点中的数值代表hash值:

2、根据TreeNode链表初始化TreeBin类对象,TreeBin在实现上同样继承了Node类,所以初始化完成的TreeBin类对象可以保持在Node数组中;

 class TreeBin<K,V> extends Node<K,V> {
TreeNode<K,V> root;
volatile TreeNode<K,V> first;
volatile Thread waiter; //volatile修饰,内存可见,
volatile int lockState;
// values for lockState
// 持有写锁时,设置为1
static final int WRITER = 1;
// 等待写锁时的设置为2
static final int WAITER = 2;
// increment value for setting read lock
static final int READER = 4;
}

3、初始化根节点:

遍历TreeNode链表生成红黑树,一开始二叉树的根节点root为空,则设置链表中的第一个节点80为root,并设置其red属性为false,因为在红黑树的特性1中,明确规定根节点必须是黑色的;

 for (TreeNode<K,V> x = b, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
if (r == null) {
x.parent = null;
x.red = false;
r = x;
}
...

二叉树结构:

4、加入节点60,如果root不为空,则通过比较节点hash值的大小将新节点插入到指定位置,实现如下:

 K k = x.key;
int h = x.hash;
Class<?> kc = null;//泛型,泛型擦除在编译期间
for (TreeNode<K,V> p = r;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
r = balanceInsertion(r, x);
break;
}
}

其中x代表即将插入到红黑树的节点,p指向红黑树中当前遍历到的节点,从根节点开始递归遍历,x的插入过程(根据hash值的大小,找到插入位置并插入)如下:

1)、如果xhash值小于phash值,则判断p的左节点是否为空,如果不为空,则把p指向其左节点,并继续和p进行比较,如果p的左节点为空,则把x指向的节点插入到该位置;

2)、如果xhash值大于phash值,则判断p的右节点是否为空,如果不为空,则把p指向其右节点,并继续和p进行比较,如果p的右节点为空,则把x指向的节点插入到该位置;

3)、如果xhash值和phash值相等,怎么办?
解决:首先判断节点中的key对象的类是否实现了Comparable接口,如果实现Comparable接口,则调用compareTo方法比较两者key的大小,但是如果key对象没有实现Comparable接口,或则compareTo方法返回了0,则继续调用tieBreakOrder方法计算dir值,tieBreakOrder方法实现如下:

static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}

最终比较key对象的默认hashCode()方法的返回值,因为System.identityHashCode(a)调用的是对象a默认的hashCode()

插入节点60之后的二叉树:

5、当有新节点加入时,可能会破坏红黑树的特性,需要执行balanceInsertion()方法调整二叉树,使之重新满足特性,方法中的变量xp指向x的父节点,xpp指向xp父节点,xpplxppr分别指向xpp的左右子节点,balanceInsertion()方法首先会把新加入的节点设置成红色。

①、加入节点60之后,此时xp指向节点80,其父节点为空,直接返回。

if ((xp = x.parent) == null) {
x.red = false;
return x;
}
else if (!xp.red || (xpp = xp.parent) == null)
return root;

调整之后的二叉树:

②、加入节点50,二叉树如下:

继续执行balanceInsertion()方法调整二叉树,此时节点50的父节点60是左儿子,走如下逻辑:

if (xp == (xppl = xpp.left)) {
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}

根据上述逻辑,把节点60设置成黑色,把节点80设置成红色,并对节点80执行右旋操作,右旋实现如下:

static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> l, pp, lr;
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null)
lr.parent = p;
if ((pp = l.parent = p.parent) == null)
(root = l).red = false;
else if (pp.right == p)
pp.right = l;
else
pp.left = l;
l.right = p;
p.parent = l;
}
return root;
}

右旋之后的红黑树如下:

③、加入节点70,二叉树如下:

继续执行balanceInsertion()方法调整二叉树,此时父节点80是个右儿子,节点70是左儿子,且叔节点50不为空,且是红色的,则执行如下逻辑:

if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}

此时二叉树如下:

此时x指向xpp,即节点60,继续循环处理x,设置其颜色为黑色,最终二叉树如下:

④、加入节点20,二叉树变化如下:

因为节点20的父节点50是一个黑色的节点,不需要进行调整;

⑤、加入节点65,二叉树变化如下:

对节点80进行右旋操作。

⑥、加入节点40,二叉树变化如下:

1、对节点20执行左旋操作;
2、对节点50执行右旋操作;

最后加入节点10,二叉树变化如下:

重新对节点进行着色,到此为止,红黑树已经构造完成;

ConcurrentHashMap红黑树的实现的更多相关文章

  1. HashMap、ConcurrentHashMap红黑树实现分析

    本文学习知识点 1.二叉查找树,以及二叉树查找带来的问题. 2.平衡二叉树及好处. 3.红黑树的定义及构造. 4.ConcurrentHashMap中红黑树的构造. 在正式分析红黑树之前,有必要了解红 ...

  2. 研究jdk关于TreeMap 红黑树算法实现

    因为TreeMap的实现方式是用红黑树这种数据结构进行存储的,所以呢我主要通过分析红黑树的实现在看待TreeMap,侧重点也在于如何实现红黑树,因为网上已经有非常都的关于红黑树的实现.我也看了些,但是 ...

  3. 【Java源码】集合类-JDK1.8 哈希表-红黑树-HashMap总结

    JDK 1.8 HashMap是数组+链表+红黑树实现的,在阅读HashMap的源码之前先来回顾一下大学课本数据结构中的哈希表和红黑树. 什么是哈希表? 在存储结构中,关键值key通过一种关系f和唯一 ...

  4. Java容器汇总【红黑树需要再次学习】

    1,概述 2,Collection 2.1,Set[接触比较少] 2.1.1 TreeSet 底层由TreeMap实现 基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作.但是查找效率不如 ...

  5. ConcurrenHashMap介绍1.8 中为什么要用红黑树

    java8不是用红黑树来管理hashmap,而是在hash值相同的情况下(且重复数量大于8),用红黑树来管理数据. 红黑树相当于排序数据.可以自动的使用二分法进行定位.性能较高. 在Concurren ...

  6. HashMap1.7和1.8,红黑树原理!

    jdk 1.7 概述 HashMap基于Map接口实现,元素以键值对的方式存储,并允许使用null键和null值,但只能有一个键作为null,因为key不允许重复,另外HashMap不能保证放入元素的 ...

  7. 红黑树——算法导论(15)

    1. 什么是红黑树 (1) 简介     上一篇我们介绍了基本动态集合操作时间复杂度均为O(h)的二叉搜索树.但遗憾的是,只有当二叉搜索树高度较低时,这些集合操作才会较快:即当树的高度较高(甚至一种极 ...

  8. jdk源码分析红黑树——插入篇

    红黑树是自平衡的排序树,自平衡的优点是减少遍历的节点,所以效率会高.如果是非平衡的二叉树,当顺序或逆序插入的时候,查找动作很可能会遍历n个节点 红黑树的规则很容易理解,但是维护这个规则难. 一.规则 ...

  9. 谈c++ pb_ds库(二) 红黑树大法好

    厉害了,没想到翻翻pb_ds库看到这么多好东西,封装好的.现成的splay.红黑树.avl... 即使不能在考场上使用也可以用来对拍哦 声明/头文件 #include <ext/pb_ds/tr ...

随机推荐

  1. 操作系统-CPU管理的直观想法

    1. 管理CPU,先要使用CPU 管理CPU的最直观方法 2. 提出问题 有IO指令执行的特别慢,当cpu执行计算指令很快,遇到IO指令cpu进行等待,利用率不高. 使用多道程序.交替执行,这样cpu ...

  2. D3.js实现拓扑图

    最近写项目需要画出应用程序调用链的网路拓扑图,完全自己写需要花费些时间,那么首先想到的是echarts,但echarts的自定义写法写起来非常麻烦,而且它的文档都是基于配置说明的,对于自定义开发不太方 ...

  3. C# 视频监控系统(提供源码分享)

    去过工厂或者仓库的都知道,在工厂或仓库里面,会有很多不同的流水线,大部分的工厂或仓库,都会在不同流水线的不同工位旁边安装一台电脑,一方面便于工位上的师傅把产品的重要信息录入系统,便于公司系统数据采集分 ...

  4. 关于Java序列化的问题你真的会吗?

    引言 在持久化数据对象的时候我们很少使用Java序列化,而是使用数据库等方式来实现.但是在我看来,Java 序列化是一个很重要的内容,序列化不仅可以保存对象到磁盘进行持久化,还可以通过网络传输.在平时 ...

  5. # 爬虫连载系列(1)--爬取猫眼电影Top100

    前言 学习python有一段时间了,之前一直忙于学习数据分析,耽搁了原本计划的博客更新.趁着这段空闲时间,打算开始更新一个爬虫系列.内容大致包括:使用正则表达式.xpath.BeautifulSoup ...

  6. JAVA校内赛

    第一题: 问题描述 在计算机存储中,15.125GB是多少MB?答案提交 这是一道结果填空的题,你只需要算出结果后提交即可.本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分. ...

  7. TCP粘包很难么,为何我屡屡受挫??

    无论走到哪里,都应该记住,过去都是假的,回忆是一条没有尽头的路,一切以往的春天都不复存在,就连那最坚韧而又狂乱的爱情归根结底也不过是一种转瞬即逝的现实. --马尔克斯 本文已经收录至我的GitHub, ...

  8. drf分页功能

    什么是restful规范 是一套规则,用于程序之间进行数据交换的约定. 他规定了一些协议,对我们感受最直接的的是,以前写增删改查需要写4个接口,restful规范的就是1 个接口,根据method的不 ...

  9. Windows下EDK2环境的搭建以及经典的程序设计Print Hello World !-----(Linux下的待后续熟练了再更新)

    很久没有更新博客了,之前的博客末尾有提到过要写有关EDK2环境搭建的博客,现在就是完成的时候了,后续博客更新会比较规律(大概每周一篇?) 本人博客仅仅发表于博客园,本人主页为         http ...

  10. C++ for循环练习

    #include <stdio.h> //题目:现有公鸡5元一只,母鸡3元一只,小鸡1元3只(小鸡不能单买 最少3只起卖),此时顾客有100元,请问有多少种购买方案. int main() ...