ConcurrentHashMap的红黑树实现分析
转载自 ConcurrentHashMap的红黑树实现分析
红黑树
红黑树是一种特殊的二叉树,主要用它存储有序的数据,提供高效的数据检索,时间复杂度为O(lgn),每个节点都有一个标识位表示颜色,红色或黑色,有如下5种特性:
1、每个节点要么红色,要么是黑色;
2、根节点一定是黑色的;
3、每个空叶子节点必须是黑色的;
4、如果一个节点是红色的,那么它的子节点必须是黑色的;
5、从一个节点到该节点的子孙节点的所有路径包含相同个数的黑色节点;
结构示意图
只要满足以上5个特性的二叉树都是红黑树,当有新的节点加入时,有可能会破坏其中一些特性,需要通过左旋或右旋操作调整树结构,重新着色,使之重新满足所有特性。
ConcurrentHashMap红黑树实现
《谈谈ConcurrentHashMap1.7和1.8的不同实现》一文中已经提到,在1.8的实现中,当一个链表中的元素达到8个时,会调用treeifyBin()
方法把链表结构转化成红黑树结构,实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/**
* 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
类;
1
2
3
4
5
6
7
8
|
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
数组中;
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class TreeBin<K,V> extends Node<K,V> {
TreeNode<K,V> root;
volatile TreeNode<K,V> first;
volatile Thread waiter;
volatile int lockState;
// values for lockState
// set while holding write lock
static final int WRITER = 1 ;
// set when waiting for write lock
static final int WAITER = 2 ;
// increment value for setting read lock
static final int READER = 4 ;
}
|
3、遍历TreeNode
链表生成红黑树,一开始二叉树的根节点root
为空,则设置链表中的第一个节点80为root
,并设置其red
属性为false
,因为在红黑树的特性1中,明确规定根节点必须是黑色的;
1
2
3
4
5
6
7
8
9
|
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
值的大小将新节点插入到指定位置,实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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
的插入过程如下:
1)、如果x
的hash
值小于p
的hash
值,则判断p
的左节点是否为空,如果不为空,则把p
指向其左节点,并继续和p
进行比较,如果p
的左节点为空,则把x
指向的节点插入到该位置;
2)、如果x
的hash
值大于p
的hash
值,则判断p
的右节点是否为空,如果不为空,则把p
指向其右节点,并继续和p
进行比较,如果p
的右节点为空,则把x
指向的节点插入到该位置;
3)、如果x
的hash
值和p
的hash
值相等,怎么办?
解决:首先判断节点中的key
对象的类是否实现了Comparable
接口,如果实现Comparable
接口,则调用compareTo
方法比较两者key
的大小,但是如果key
对象没有实现Comparable
接口,或则compareTo
方法返回了0,则继续调用tieBreakOrder
方法计算dir
值,tieBreakOrder
方法实现如下:
1
2
3
4
5
6
7
8
9
|
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
父节点,xppl
和xppr
分别指向xpp
的左右子节点,balanceInsertion()
方法首先会把新加入的节点设置成红色。
①、加入节点60之后,此时xp
指向节点80,其父节点为空,直接返回。
1
2
3
4
5
6
|
if ((xp = x.parent) == null ) {
x.red = false ;
return x;
}
else if (!xp.red || (xpp = xp.parent) == null )
return root;
|
调整之后的二叉树:
②、加入节点50,二叉树如下:
继续执行balanceInsertion()
方法调整二叉树,此时节点50的父节点60是左儿子,走如下逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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执行右旋操作,右旋实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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不为空,且是红色的,则执行如下逻辑:
1
2
3
4
5
6
|
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的红黑树实现分析相关推荐
- 死磕Java并:J.U.C之ConcurrentHashMap红黑树转换分析
作者:chessy 来源:Java技术驿站 在[死磕Java并发]-----J.U.C之Java并发容器:ConcurrentHashMap一文中详细阐述了ConcurrentHashMap的实现过程 ...
- ConcurrentHashMap 红黑树转换分析
ConcurrentHashMap 红黑树转换分析 先看红黑树的基本概念:红黑树是一课特殊的平衡二叉树,主要用它存储有序的数据,提供高效的数据检索,时间复杂度为O(lgn).红黑树每个节点都有一个标识 ...
- HashMap红黑树原理分析
近期学习了 HashMap 实现原理,这篇咱们了解一下红黑树的设计,相比 jdk1.7 的 HashMap 而言,jdk1.8最重要的就是引入了红黑树的设计,当hash表的单一链表长度超过 8 个的时 ...
- ConcurrentHashMap的红黑树
红黑树 红黑树是一种特殊的二叉树,主要用它存储有序的数据,提供高效的数据检索,时间复杂度为O(lgn), 每个节点都有一个标识位表示颜色,红色或黑色,有如下5种特性: 1.每个节点要么红色,要么是黑色 ...
- 为什么红黑树查询快_目前最详细的红黑树原理分析(大量图片+过程推导!!!)...
一.为什么要有红黑树这种数据结构? 我们知道ALV树是一种严格按照定义来实现的平衡二叉查找树,所以它查找的效率非常稳定,为O(log n),由于其严格按照左右子树高度差不大于1的规则,插入和删除操作中 ...
- 数据结构找你妹(一)从二叉树到红黑树的分析实现
什么是查找? 好了,我们今天的主题是--找你妹.或许应该把话题提升到一个不那么"好听"的层次--查找.但还是从"好听"的讲起吧,我们应该都玩过"找你妹 ...
- 目前最详细的红黑树原理分析(大量图片+过程推导!!!)
一.为什么要有红黑树这种数据结构? 我们知道ALV树是一种严格按照定义来实现的平衡二叉查找树,所以它查找的效率非常稳定,为O(log n),由于其严格按照左右子树高度差不大于1的规则,插入和删除操 ...
- 数据结构-红黑树原理分析
前言 在阅读HashMap源码的时候发现,java1.8的HashMap的链表实现增加了红黑树,当链表长度超过指定阈值8的时候回进行树化. 为了提高增删查的效率. 而红黑树又比较复杂,所以专门写一篇关 ...
- Java高级开发面试,红黑树详细分析(图文详解)
开头 如果Redis的读写请求量很大,那么单个实例很有可能承担不了这么大的请求量,如何提高Redis的性能呢?你也许已经想到了,可以部署多个副本节点,业务采用读写分离的方式,把读请求分担到多个副本节点 ...
最新文章
- Salesforce发布人工智能工具分析社交媒体内容
- 路由器无服务器无响应是怎么回事啊,wifi服务器无响应怎么解决(图文)
- html实现全屏效果原理,HTML5 实现全屏效果
- LinearLayout测量原理解析:onMeasure(int,int)
- C# 多线程六之Task(任务)三之任务工厂
- Golang与C#之switch区别
- PL/SQL工具执行SQL脚本文件
- java随机数排序算法_理解快速排序算法
- spark学习-64-源代码:schedulerBackend和taskScheduler的创建(2)-StandLone
- 构建插件式的应用程序框架(四)----服务容器
- 20150409作业3 阅读《构建之法》1-5章
- 详解JavaScript的闭包
- php公物管理系统,开源客户管理系统 fly-crm
- php简单答题系统,念做个简易php选择题答题系统
- 计算机的存储器(详解)
- 云测平台iOS环境搭建
- dell最新计算机如何U盘引导,2018戴尔最新版电脑bios设置u盘启动教程
- 转发器、网桥、路由器和网关的区别如下:
- PHPMyWind支持ppt一键导入
- 二、Tools实用工具-FinalShell 纯国产可同步的ssh+ftp工具
热门文章
- 高等数学下-赵立军-北京大学出版社-题解-练习9.3
- 《C++ Primer》13.1.3节练习
- HDU - 2444——The Accomodation of Students(判断二分图,二分图最大匹配)
- cma检测_CMA检测方法
- math:线性代数之行列式
- python通过tkinter和json界面库实现考研知识点统计
- Educational Codeforces Round 90 (Rated for Div. 2)(A, B, C, D, E)
- #3027. [Ceoi2004]Sweet 生成函数 + 组合数学
- Codeforces Round #709 (Div. 1) C. Skyline Photo dp + 单调栈优化
- 2011年全国大学生程序设计邀请赛(福州)