JDK1.8中关于HashMap的红黑树讲解

2023-11-05

一,首先需要了解以下几个问题:

1.为什么要引入红黑数(特殊的平衡二叉树)数据结构

2.引入红黑树HashMap做了哪些改造

3.  红黑树的特性

4.红黑树的具体实现方式

二,逐一解释以上三个问题

1.1 为什么要引入红黑数(特殊的平衡二叉树)数据结构

由于在JDK1.7之前,HashMap的数据结构为:数组 + 链表。数组相当于日常中永到的数据结构Array. 用来确定key-value对所存储的位置。那么为什么又有链表结构?这个要从HashMap散列值生成来讲起。这个具体细节可参考相关文档即可。如果按照Hash值,通过Hash函数来确认桶位,会存在一个问题,就是hash冲突的问题,也就是不同的key可能会产生不一样的hash值。

static final int hash(Object key) {
    int h;
    // 两个值做异或,最终相同的可能性很大
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

 

所以引入了链表来存储hash值一样的key-value. 如果按照链表的方式存储,随着节点的增加数据会越来越多,这会导致查询节点的时间复杂度会逐渐增加,平均时间复杂度O(n)。 为了提高查询效率,故在JDK1.8中引入了改进方法红黑树。此数据结构的平均查询效率为O(log n) 。

1.2 引入红黑树HashMap做了哪些改造

当链表节点长度超过8时,将链表转换为二叉树。

 
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K, V>[] tab;
    Node<K, V> p;
    int n, i;
    if ((tab = table) == null || (n = tab.length) == 0) {
        n = (tab = resize()).length;
    }
    if ((p = tab[i = (n - 1) & hash]) == null) {
        tab[i] = newNode(hash, key, value, null);
    } else {
        Node<K, V> e;
        K k;
        if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k)))) {
            e = p;
        } else if (p instanceof TreeNode) {
            e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
        } else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    // 先将新节点插入到 p.next
                    p.next = newNode(hash, key, value, null);
                    // 如果长度链表长度超过8,则转换为二叉树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
 
                    /**
                     * hash 需要转化二叉树的hash值
                     */
                    // 转化为二叉树
                    {
                        treeifyBin(tab, hash);
                    }
                    break;
                }
                // 存在hash和key都一样的情况,则说明已经存在。直接跳出循环
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) {
                    break;
                }
                // 继续下一次循环
                p = e;
            }
        }
 
        //
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null) {
                e.value = value;
            }
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold) {
        resize();
    }
    afterNodeInsertion(evict);
    return null;
}

1.3  红黑树的特性

1.3.1 什么时红黑树

红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。《引自百度百科》

1.3.2 红黑树的特点

红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。

1.3.3 红黑树特点

性质1:节点是红色或黑色。
性质2:根节点是黑色。
性质3:每个叶节点(NIL节点,空节点)是黑色的。
性质4:每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5:从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

//数据结构
static final class TreeNode<K,V> extends LinkedHashMap.Entry<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(int hash, K key, V val, Node<K,V> next) {
		super(hash, key, val, next);
	}
	....
}

1.4 红黑树的具体实现方式(重点)

在JDK1.8 HashMap中,转换为红黑树大致分为三个步骤。

第一阶段:将链表转化为二叉树

第二阶段:验证是否满足红黑树的五大特征

第三阶段:对二叉树进行左右旋转操作

1.4.1  将链表转化为二叉树

final void treeifyBin(Node<K,V>[] tab, int hash) {
	int n, index; Node<K,V> e;
	if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
		resize();
	// 重新计算 hash段位,及table的索引位,第一个节点
	else if ((e = tab[index = (n - 1) & hash]) != null) {
 
		/************ 双向链表 start***************/
		// hd头节点, tl尾节点
		TreeNode<K,V> hd = null, tl = null;
		do {
			// 循环所有节点
			TreeNode<K,V> p = replacementTreeNode(e, null);
			if (tl == null)
				hd = p;
			else {
				p.prev = tl;
				tl.next = p;
			}
			tl = p;
		} while ((e = e.next) != null);// 循环下一个节点
		/************ 双向链表 end***************/
 
 
		// 前面仅仅转换为双向链表,treeify才是转换红黑树的处理方法入口 
		// 第一个节点赋值为头节点,也就是根节点
		if ((tab[index] = hd) != null)
			// 将二叉树转换为红黑树
			hd.treeify(tab);
	}
}

1.4.2 验证是否满足红黑树的五大特征 

/**
 * 调用这个方法之前 也就是一个双向链表
 * 初始进入值为 this头节点
 * 将双向链表转换为红黑树
 * 目标:查询 root 节点
 * @param tab
 */
final void treeify(Node<K,V>[] tab) {
	TreeNode<K,V> root = null;//root节点
	for (TreeNode<K,V> x = this, next; x != null; x = next) {
		next = (TreeNode<K,V>)x.next; //next 下一个节点
		x.left = x.right = null;//设置左右节点为空
		if (root == null) {//首次循环 root == null
			x.parent = null; // 将根节点的父节点设置位空
			x.red = false; // 将根节点设置为 black
			root = x; //将x 设置为根节点
		}
		else {// 非根节点
			K k = x.key;// 获取当前循环节点key
			int h = x.hash;// 获取当前节点hash
			Class<?> kc = null;
			// 从根节点开始验证
			for (TreeNode<K,V> p = root;;) {
				int dir, ph;
				K pk = p.key;// 每个节点的key
				if ((ph = p.hash) > h) //每个节点的hash 与 外层循环的x.hash做比较
					dir = -1;// <0 ,沿左路径查找 -1
				else if (ph < h)// >0, 沿右路径查找 1
					dir = 1;
 
				// 如果存在比较对象,则根据比较对象定义的comparable进行比较
				// 比较之后返回查询节点路径(左或右)
				else if ((kc == null &&
						(kc = comparableClassFor(k)) == null) ||
						(dir = compareComparables(kc, k, pk)) == 0)
					dir = tieBreakOrder(k, pk);
 
				// p设置位x的父节点 xp
				TreeNode<K,V> xp = p;
 
				// 如果父节点的左节点或右节点为空时,才进行插入操作
				if ((p = (dir <= 0) ? p.left : p.right) == null) {
					// 将px设置为x的父节点
					x.parent = xp;
					if (dir <= 0)
						xp.left = x;
					else
						xp.right = x;
					// 将二叉树转换位红黑树-正式转换红黑树
					root = balanceInsertion(root, x);
					break;
				}
			}
		}
	}
	moveRootToFront(tab, root);
}

1.4.3 对二叉树进行左右旋转操作

/**
 * 转换二叉树为红黑树
 * @param root 根节点
 * @param x 执行的节点
 * @param <K>
 * @param <V>
 * @return
 */
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
											TreeNode<K,V> x) {
	// 默认x节点为红色节点
	x.red = true;
 
	/**
	 * xp:   x的父节点
	 * xpp:  x父节点的父节点
	 * xppl: x父节点的父节点左子节点
	 * xppr: x父节点的父节点右子节点
	 */
	for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
 
		// xp = x.parent
		// 如果x存在父节点,则说明目前只有一个节点,即root.根据
		// 红黑树的五大特征,根节点只能为黑色节点
		if ((xp = x.parent) == null) {
			x.red = false;
			return x;
		}
 
		//xpp = xp.parent
		//直接查询的是根节点
		else if (!xp.red || (xpp = xp.parent) == null)
			return root;
 
		// xppl = xpp.left
		// x的父节点时左节点时
		if (xp == (xppl = xpp.left)) {
 
			// 验证是否需要旋转
			// xppr = xpp.right 存在右节点 且 右节点为红色
			if ((xppr = xpp.right) != null && xppr.red) {
				xppr.red = false; // xppr 设置位black
				xp.red = false; // xp 设置位black
				xpp.red = true; // xpp 设置位red
				x = xpp;// 将x赋值为父节点的父节点
			}
			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);
					}
				}
			}
		}
 
		// x的父节点右节点时
		else {
 
			// 验证是否需要旋转
			if (xppl != null && xppl.red) {
				xppl.red = false;
				xp.red = false;
				xpp.red = true;
				x = xpp;
			}
			else {
				if (x == xp.left) {
 
					// 右旋转
					root = rotateRight(root, x = xp);
					xpp = (xp = x.parent) == null ? null : xp.parent;
				}
				if (xp != null) {
					xp.red = false;
					if (xpp != null) {
						xpp.red = true;
 
						// 左旋转
						root = rotateLeft(root, xpp);
					}
				}
			}
		}
	}
}

1.4.3.1 左旋转

/**
 * 左旋转
 * @param root
 * @param p
 * @param <K>
 * @param <V>
 * @return
 */
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
									  TreeNode<K,V> p) {
	TreeNode<K,V> r, pp, rl;
	if (p != null && (r = p.right) != null) {
		if ((rl = p.right = r.left) != null)
			rl.parent = p;
		if ((pp = r.parent = p.parent) == null)
			(root = r).red = false;
		else if (pp.left == p)
			pp.left = r;
		else
			pp.right = r;
		r.left = p;
		p.parent = r;
	}
	return root;
}

1.4.3.2 右旋转

/**
 * 右旋转
 * @param root
 * @param p
 * @param <K>
 * @param <V>
 * @return
 */
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
									   TreeNode<K,V> p) {
 
	// l: p的左节点  pp:p的父节点 lr:左右节点
	TreeNode<K,V> l, pp, lr;
 
	// 传入参数
	// root: 默认调用此方法前指定的root节点
	// p: root的父节点
	if (p != null && (l = p.left) != null) {
 
		if ((lr = p.left = l.right) != null)
			lr.parent = p;
 
		// 判断p的父节点是否为空
		if ((pp = l.parent = p.parent) == null)
			// 调整root的值
			(root = l).red = false;
 
 
		else if (pp.right == p)
			pp.right = l;
		else
			pp.left = l;
 
		// 将p调整为 root 节点的右节点
		l.right = p;
 
		//将l调整为p的parent
		p.parent = l;
	}
 
	return root;
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JDK1.8中关于HashMap的红黑树讲解 的相关文章

  • 浅拷贝&深拷贝

    什么是深 浅拷贝 他们跟赋值有什么关系 浅拷贝 浅拷贝是创建一个新对象 这个对象有着原始对象属性值的一份精确拷贝 如果属性是基本类型 拷贝的就是基本类型的值 如果属性是引用类型 拷贝的就是内存地址 所以如果其中一个对象改变了这个地址 就会影
  • 算法 堆排序 heapSort

    堆排序 heapSort 堆是一种数据结构 一种叫做完全二叉树的数据结构 堆排序是利用堆数据结构而设计的一种排序算法 堆排序是一种选择排序 其最坏 最好 平均时间复杂度均为O nlogn 同时也是不稳定排序 堆的性质 这里我们用到两种堆 其
  • Error 12154 received logging on to the standby

    环境为 操作系统 AIX 6 1 oracle版本 11 2 0 3 psu5 本编记录了本人一次DG搭建失败的经历 只是一个小的错误 但却导致我排查了半天 记录本次经历用来警醒自己 作为一名dba任何时候我们都要万分仔细 认真 DG搭建的

随机推荐

  • Codeforces 102263C-Check The Text【模拟】 难度:*

    题意 Roze has a special keyboard which consists only of 29 keys 26 alphabetic a z keys which prints the 26 lowercase Latin
  • buck电路_基本斩波电路---BUCK电路

    BUCK电路又称降压斩波电路 其工作原理图如图1所示 该电路有输入直流电源Vin 一个全控器件M1 续流二极管D1 电感L1 负载R1 其主要用于电路的供电电源及带蓄电池负载 拖动直流电机等 在拖动直流电机及蓄电池负载时会出现反电动势 如图
  • 简易版 图书管理系统

    目录 1 Book包 1 1 Book类 1 2 BookList类 2 User包 2 1 User抽象类 2 2 AdminUser类 2 3 NormalUser类 3 Operate包 3 1 MyOperate接口 3 2 Add
  • 【操作系统】

    请解释下局部性原理 通俗易懂的解释 二维数组两维不同遍历的实例 关于CPU三级缓存的介绍 b
  • bes2300 tws配对_tws 耳机春天来了!

    本人负责蓝牙耳机音箱厂家的市场拓展 经常接触国内外各种卖家 专注亚马逊 京东的精品卖家 也有日本 欧美等线下渠道等的B端客户 当然作为行业的业务 也经常接触同行工厂 购买同行的产品或者借用产品用来测试 所以对产品的发展技术趋势及产品的体验感
  • 一文详解Spring事务传播机制

    目录 背景 Spring事务 Transactional注解 使用场景 失效场景 原理 常用参数 注意 事务传播机制 处理嵌套事务流程 主事务为REQUIRED子事务为REQUIRED 主事务为REQUIRED子事务为REQUIRES NE
  • Android JNI 打印日志

    首先要保证Cmake文件中有log模块引用 不然编译不通过 编译一个库 add library native lib 库的名字 SHARED 动态库 so库 native lib cpp 需要编译的C 文件 相当于定义一个变量log lib
  • 内核模式驱动程序的网络结构

    转载请标明是引用于 http blog csdn net chenyujing1234 欢迎大家拍砖 1 Windows 2000 网络结构和OSI模型 Windows 2000网络结构是以国际标准化组织 ISO 制定的七层网络模型为基础的
  • 刷脸支付助力商家吸引客户增加客流量

    顾客支付完成后 直接领取会员卡 保存到微信卡包 可以作为充值卡 积分卡 打折卡 商家可以进行自定义 使用方便 不易丢失 灵活度高 同时商家还可以设置一键分享会员卡 通过老顾客转介绍有礼 快速增加会员 商家可自行发送优惠劵 通过刷脸支付即可领
  • ubuntu添加vlan和路由追踪

    1 linux添加vlan子接口 安装vconfig命令 apt get install vlan vconfig add eth0 105 eth0为物理网络接口名称 ifconfig eth0 105 192 168 105 10 对上
  • HTML 5 Canvas vs. SVG

    一 SVG SVG 是一种使用 XML 描述 2D 图形的语言 SVG 基于 XML 这意味着 SVG DOM 中的每个元素都是可用的 您可以为某个元素附加 JavaScript 事件处理器 在 SVG 中 每个被绘制的图形均被视为对象 如
  • 面试题 02.03. 删除中间节点

    面试题 02 03 删除中间节点 删除中间节点 这道题是说只能访问中间某个位置的节点 然后要把它删除 由于没给前驱指针 如果真的删了中间的节点 链表就断开了 所以考虑把下一个节点的值赋给当前节点 然后删除下一个节点 这样就相当于删除了中间节
  • 幸福感

    抱怨 坦荡的看自己 做自己 我有什么 我要什么 我愿意放弃什么 公平 我们过这一生是为什么呢 世界什么时公平与不公平呢 人最宝贵的 客观的 生命和时间 心态 幸福感来自于心态 一个乐观的心态 每天都会发生很多事情让自己很浮躁 有时会觉得身心
  • [1042]JWT加密

    文章目录 简介 起源 数据格式 JWT交互流程 python使用jwt 非对称加密 简介 JWT 全称是Json Web Token 是JSON风格轻量级的授权和身份认证规范 可实现无状态 分布式的Web应用授权 jwt的解析工具 http
  • 有人物联口红DTU DR154配置与RS 485传感器数据处理

    一 硬件设备 1 有人物联口红DTU DR154 RS 485版本 配置的话通过小程序 联博士 蓝牙配置 手机扫描DTU背后的二维码即可 蓝牙密码也在背面 省去了连接电脑硬件配置的繁琐步骤 2 温度传感器 速灵科RSDS5 RS485接口
  • C语言位运算符及作用:与、或、异或、取反、左移和右移

    一 按位与 如果两个相应的二进制位都为1 则该位的结果值为1 否则为0 应用 1 清零 若想对一个存储单元清零 即使其全部二进制位为0 只要找一个二进制数 其中各个位符合一下条件 原来的数中为1的位 新数中相应位为0 然后使二者进行 运算
  • java反编译篇--miaow.Y.Hu

    好了 好久没更新了 今天来一篇关于java反编译的篇举例子 Java编译文件是指通过Java编译器将Java源代码文件 java文件 编译成字节码文件 class文件 的过程 在Java中 源代码文件包含人类可读的文本 而字节码文件是计算机
  • 常见分布的密度函数图像

    常见分布的密度函数图像 作者 凯鲁嘎吉 博客园 http www cnblogs com kailugaji 一 gamma分布 1 2 3 二 beta分布 1 a 1 2 b 1 3 三 卡方分布 四 t分布 五 F分布 1 n1 5
  • 垃圾分类小程序(含源码)

    前言 鉴于前期一段时间 自己接触小程序这块儿 根据当时的政策 随编写了垃圾分类小程序 在后期也做了很多的功能迭代 基本满足目前市面上的使用需求 期间也对当时垃圾分类的方案进行了简单汇总 可回收物 可回收物就是再生资源 指生活垃圾中未经污染
  • JDK1.8中关于HashMap的红黑树讲解

    一 首先需要了解以下几个问题 为什么要引入红黑数 特殊的平衡二叉树 数据结构 引入红黑树HashMap做了哪些改造 红黑树的特性 红黑树的具体实现方式 二 逐一解释以上三个问题 1 1 为什么要引入红黑数 特殊的平衡二叉树 数据结构 由于在