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.
 */
privatefinal void treeifyBin(Node<K,V>[] tab, intindex) {
    Node<K,V> b; intn, sc;
    if(tab != null) {
        if((n = tab.length) < MIN_TREEIFY_CAPACITY)
            tryPresize(n << 1);
        elseif ((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 =
                            newTreeNode<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, newTreeBin<K,V>(hd));
                }
            }
        }
    }
}

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

红黑树构造过程

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

1
2
3
4
5
6
7
8
classTreeNode<K,V> extendsNode<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
    booleanred;
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
classTreeBin<K,V> extendsNode<K,V> {
    TreeNode<K,V> root;
    volatileTreeNode<K,V> first;
    volatileThread waiter;
    volatileint lockState;
    // values for lockState
    // set while holding write lock
    staticfinal int WRITER = 1;
    // set when waiting for write lock
    staticfinal int WAITER = 2;
    // increment value for setting read lock
    staticfinal 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;
inth = x.hash;
Class<?> kc = null;
for(TreeNode<K,V> p = r;;) {
    intdir, ph;
    K pk = p.key;
    if((ph = p.hash) > h)
        dir = -1;
    elseif (ph < h)
        dir = 1;
    elseif ((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)、如果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方法实现如下:

1
2
3
4
5
6
7
8
9
staticint tieBreakOrder(Object a, Object b) {
    intd;
    if(a == null|| b == null||
        (d = a.getClass().getName().
         compareTo(b.getClass().getName())) == 0)
        d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
             -1: 1);
    returnd;
}

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

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

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

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

1
2
3
4
5
6
if((xp = x.parent) == null) {
    x.red = false;
    returnx;
}
elseif (!xp.red || (xpp = xp.parent) == null)
    returnroot;

调整之后的二叉树:

②、加入节点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;
        elseif (pp.right == p)
            pp.right = l;
        else
            pp.left = l;
        l.right = p;
        p.parent = l;
    }
    returnroot;
}

右旋之后的红黑树如下:

③、加入节点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,二叉树变化如下:

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


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

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

相关文章

leetcode初级算法3.存在重复元素

leetcode初级算法3.存在重复元素 仅为个人刷题记录&#xff0c;不提供解题思路 题解与收获 我的解法&#xff1a; Arrays.sort(nums);for(int i 0; i < nums.length-1; i){if(nums[i] nums[i1]){return true;}}return false;官方题解&#xff1a; public boolean con…

sql server累计求和函数_SQL基础--SQL高级功能

一.窗口函数有什么用&#xff1f;在日常工作中&#xff0c;经常会遇到需要在每组内排名&#xff0c;比如下面的业务需求&#xff1a;排名问题&#xff1a;每个部门按业绩来排名topN问题&#xff1a;找出每个部门排名前N的员工进行奖励面对这类需求&#xff0c;就需要使用sql的高…

(转)【SpringMvc】如何使用form发送PUT和DELETE请求

转自&#xff1a; https://blog.csdn.net/cockroach02/article/details/82194126https://blog.csdn.net/cockroach02/article/details/82194126 一、当前现状 浏览器使用form提交信息的时候只支持GET和POST&#xff0c;如果需要在浏览器上使用PUT和DELETE请求方式的话&#…

leetcode初级算法4.只出现一次的数字

leetcode初级算法4.只出现一次的数字 仅为个人刷题记录&#xff0c;不提供解题思路 题解与收获 我的解法&#xff1a; public static int singleNumber(int[] nums) {if(nums.length 1){return nums[0];}Arrays.sort(nums);int slow 0;int fast 1;while(fast < nums.…

集合总结(Collection)

转载自 集合总结(Collection) 最近项目上线完&#xff0c;闲来无事&#xff0c;整理了关于集合相关对比&#xff0c;具体详见以下几点&#xff1a;1.ArrayList和Vector区别&#xff1a;这两个类都实现了List接口(List接口继承了Collection接口)&#xff0c;他们都是有序集合&am…

springboot接收浏览器发送delete请求( method not allowed 405解决方法)

【README】 浏览器使用form提交信息的时候只支持GET和POST&#xff0c;如果需要在浏览器上使用PUT和DELETE请求方式的话&#xff0c;只能使用欺骗的方式了&#xff0c;SpringMvc提供了HiddenHttpMethodFilter类来提供支持&#xff1b; 【1】前端 1&#xff09;list.html <…

tensorflow图形检测_社交距离检测器——Tensorflow检测模型设计

在隔离期间&#xff0c;我花时间在github上探索Tensorflow的大量预训练模型。这样做时&#xff0c;我偶然发现了一个包含25 个带有性能和速度指标的预训练对象检测模型的存储库。拥有一些计算机视觉知识并给出了实际的背景知识&#xff0c;我认为使用其中之一来构建社交隔离应用…

leetcode初级算法4.两个数组的交集 II

leetcode初级算法4.两个数组的交集 II 仅为个人刷题记录&#xff0c;不提供解题思路 题解与收获 我的解法&#xff1a;&#xff08;总结在代码中&#xff09; public int[] intersect(int[] nums1, int[] nums2) {//为空则返回if(nums1 null || nums2 null){return null;…

Java NIO:Buffer、Channel 和 Selector

转载自 Java NIO&#xff1a;Buffer、Channel 和 Selector本文将介绍 Java NIO 中三大组件 Buffer、Channel、Selector 的使用。 本来要一起介绍非阻塞 IO 和 JDK7 的异步 IO 的&#xff0c;不过因为之前的文章真的太长了&#xff0c;有点影响读者阅读&#xff0c;所以这里将它…

(转)使用IDEA将普通MAVEN项目转为WEB项目

转自&#xff1a; 使用IDEA将普通MAVEN项目转为WEB项目_yun0000000的博客-CSDN博客使用IDEA将普通MAVEN项目转为WEB项目https://blog.csdn.net/yun0000000/article/details/70664944 1、file--project Structure--,然后点“”号&#xff0c;,若没有war包&#xff0c;可修改mav…

python创建文件对象_python基础教程:文件读写

在Linux系统中&#xff0c;一切都是文件。但我们通常说的文件是保存在磁盘上的图片、文档、数据、程序等等。而在程序的IO操作中&#xff0c;很多时候就是从磁盘读写文件。本节我们讲解Python中的文件对象如何操作文件。创建文件对象 通过Python内置函数open()可以很容易的创建…

(转)springboot:添加JSP支持

转自&#xff1a; 14.springboot:添加JSP支持 - 简书&#xff08;1&#xff09;创建Maven web project 使用Eclipse新建一个Maven Web Project &#xff0c;项目取名为&#xff1a;spring-boot-jsp &#xff08;2&#xff09;在pom.xm...https://www.jianshu.com/p/4216bbd1e0…

leetcode初级算法5.加一

leetcode初级算法5.加一 仅为个人刷题记录&#xff0c;不提供解题思路 题解与收获 我的解法&#xff1a;&#xff08;总结在代码中&#xff09; public int[] plusOne(int[] digits) {//获取digits长度int length digits.length;//判断条件int count 0;//全是9的情况for …

epoll 浅析以及 nio 中的 Selector

转载自 epoll 浅析以及 nio 中的 Selector首先介绍下epoll的基本原理&#xff0c;网上有很多版本&#xff0c;这里选择一个个人觉得相对清晰的讲解&#xff08;详情见reference&#xff09;&#xff1a;首先我们来定义流的概念&#xff0c;一个流可以是文件&#xff0c;socket&…

转-SpringBoot——使用外置的Tomcat服务器

转自&#xff1a; SpringBoot——使用外置的Tomcat服务器_架构师的小跟班的博客-CSDN博客_springboot使用外置tomcat1 前言2 修改步骤2.1 修改打包方式&#xff08;jar -> war&#xff09;2.2 排除 SprignBoot的Web模块中的Tomcat依赖2.2.1 将嵌入的Tomcat依赖方式改成 pro…

leetcode初级算法6.字符串转整数(atoi)

leetcode初级算法6.字符串转整数(atoi) 仅为个人刷题记录&#xff0c;不提供解题思路 题解与收获 我的解法&#xff1a; public int myAtoi(String s) {//避免魔法值先设spaceString space " ";//如果是空或者是一串空字符串就滚回去&#xff01;if(s null || …

inner join on 加条件和where加条件_SQL学习笔记 - GROUP BY / JOIN / UNION

最近在DataCamp上学习SQL&#xff08;基于PostgreSQL&#xff09;的课程&#xff0c;本文主要记录自己易记混的点&#xff0c;以便日后参考学习&#xff0c;不做原理讲解。GROUP BY&#xff08;分组&#xff09;一般和聚合函数一起使用&#xff0c;包括COUNT()&#xff0c;AVG(…

Selector 实现原理

转载自 Selector 实现原理概述 Selector是NIO中实现I/O多路复用的关键类。Selector实现了通过一个线程管理多个Channel&#xff0c;从而管理多个网络连接的目的。 Channel代表这一个网络连接通道&#xff0c;我们可以将Channel注册到Selector中以实现Selector对其的管理。一个C…

转: 深入浅出-网络七层模型

转自 深入浅出&#xff0d;网络七层模型 - sunsky303 - 博客园引言 今天回顾一下&#xff0d;&#xff0d;网络七层模型&&网络数据包 网络基本概念 OSI模型 OSI 模型(Open System Interconnection model)是一个由国际标准化组织&#https://www.cnblogs.com/sunsky3…

date转timestamp格式_技术分享 | MySQL:timestamp 时区转换导致 CPU %sys 高的问题

作者&#xff1a;高鹏文章末尾有他著作的《深入理解 MySQL 主从原理 32 讲》&#xff0c;深入透彻理解 MySQL 主从&#xff0c;GTID 相关技术知识。本文为学习记录&#xff0c;可能有误请谅解。本文建议PC端观看&#xff0c;效果更佳。这个问题是一个朋友遇到的风云&#xff0c…