HashMap第3讲——JDK1.8红黑树细节

news2024/11/23 15:07:22

上篇文章对HashMap的put方法进行了源码解析,并介绍了其中的两个亮点设计——位运算取代%和扰动计算。其中还有几个细节,比如每次扩容都是2^n是怎么做到的、JDK1.8增加的红黑树结构,由于篇幅原因没有介绍,本节就先来介绍其中的一个细节——红黑树。

一、JDK 1.8为什么增加树结构

上节也提到过,HashMap是通过链地址法解决哈希冲突的,也就是当发生冲突时,新的元素会挂到当前桶的链表中。

这样就会有一个问题,当hash冲突过多,链表就会挂的很长,我们知道,链表越长,它的查询性能就无限趋于O(N),这样显然是不能接收的。

虽然JDK 1.8和1.7的扰动计算,尽可能地使key分散开来,但是效果并不理想,所以当链表过长的时候,就只能对其数据结构进行修改来增加效率。

毫无疑问,最先想到的就是树结构,那么树结构有很多选择,为什么最终选择了红黑树呢?

二、为什么选红黑树

2.1 二查查找树

关于查询效率,我们首先想到的应该使二叉查找树,它的特点就是左节点<root节点<右节点,这样遍历的复杂度就从链表的O(N)变为了O(logN)

但是,但在极端情况下二查查找树会退化为链表,当子节点都比父节点都大或都小时

 

2.2 平衡二叉树(AVL树)

二查查找树在最差的情况下竟然和顺序查找效率相当,这是无法接受的。我们再来看看平衡二叉树。

AVL是严格平衡的二叉树(平衡因子不超过1),每一次插入数据会检查每个节点的左子树和右子树的高度差,如果大于1,就需要进行左旋或右旋操作,因此查询效率最好,最坏的情况都是O(logN)。

不过这是有代价的:

  • 插入操作:由于AVL必须严格保证平衡,那么每次插入数据,如果平衡因子大于1,就必须进行旋转操作,事实上每次插入操作最多只需要旋转1次,所以插入操作的代价仍是O(logN)。

  • 删除操作:AVL删除可以参考二叉树的删除,但是删除之后必须检查从删除节点开始到根节点路径上的所有结点的平衡因子,这样代价就比较大了,每次删除最多需要O(logN)次旋转,因此删除操作的复杂度为O(2logN)。

综上可以发现,对于那些频繁删除和插入的场景,AVL树显然不合适,所以就引入了红黑树。

2.3 红黑树

平衡二叉树严格平衡的策略是以牺牲插入和删除操作为代价,换来的查询效率(O(logN)。是否能找一个这种的策略,既不会让插入和删除操作牺牲太大的代价,又能把查询的效率稳定在O(logN)呢?

当然有,那就是红黑树,先来看下它的特点:

  • 每个节点不是黑色就是红色,根节点永远是黑色。

  • 所有的叶子结点都是黑色的空节点,也就是叶子节点不存数据。

  • 当一个节点是红色,那么它的子节点必定是黑色的。

  • 一个节点的所有子孙节点到该节点的所有路径上,包含相同数目的黑色节点。

如下图:

我们来对比下AVL树:

  • 查询操作:红黑树不像AVL一样是严格平衡的,但查找的效率基本可以维持在O(logN),最差的情况下(最长路径是最短路径的2n-1),比AVL逊色。

  • 插入操作:插入节点时,需要旋转和变色操作。但只需要保证基本平衡就可以了,因此插入节点最多只需要2次旋转,虽然变色需要O(logN),但变色操作十分简单,代价很小。

  • 删除操作:在删除上就比AVL好多了,删除一个节点最多只需要3次旋转操作。

综上,这就是HashMap选择红黑树的原因。

三、红黑树和链表的转换时机

OK,我们回到代码来,通过上一节我们知道,数组长度大于64&&链表长度大于8,会转为红黑树,我们背过八股文的都知道,当红黑树节点小于等于6时又会转为链表,那么这些临界值是怎样考量的呢?

3.1 红黑树转链表的时机

为什么组长度大于64 && 链表长度大于8,才转为红黑树呢?

ps:首先明确一点,红黑树占用的空间是链表的2倍。

我们知道当数组长度小于64时,不会转为红黑树,而是会选择扩容,这也不难想到,因为数组长度小于64时,说明目前的数据量比较小,没必要转为红黑树。

而链表长度大于8呢?我们先来看一段源码注释:

/* Because TreeNodes are about twice the size of regular nodes, we
* use them only when bins contain enough nodes to warrant use
* (see TREEIFY_THRESHOLD). And when they become too small (due to
* removal or resizing) they are converted back to plain bins.  In
* usages with well-distributed user hashCodes, tree bins are
* rarely used.  Ideally, under random hashCodes, the frequency of
* nodes in bins follows a Poisson distribution
* (http://en.wikipedia.org/wiki/Poisson_distribution) with a
* parameter of about 0.5 on average for the default resizing
* threshold of 0.75, although with a large variance because of
* resizing granularity. Ignoring variance, the expected
* occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
* factorial(k)). The first values are:
*
* 0:    0.60653066
* 1:    0.30326533
* 2:    0.07581633
* 3:    0.01263606
* 4:    0.00157952
* 5:    0.00015795
* 6:    0.00001316
* 7:    0.00000094
* 8:    0.00000006
* more: less than 1 in ten million      
*/

大概意思就是:

理想情况下,使用随机的哈希码,节点分布在hash桶中的频率遵循泊松分布,按照泊松分布的公式计算,因为哈希冲突造成桶的链表长度为8时的概率只有0.00000006,这个概率足够低了,并且到8个节点时,红黑树的性能优势也会开始展现出来,因此8是一个比较合理的数字。

3.2 链表转红黑树的时机

对于同一个索引位置,当红黑树节点小于等于6时,就会触发红黑树转链表。(这个扩容的时候会讲)。

经过3.1小节的介绍,这也不难理解,主要原因还是对于性能和空间的考虑,那为什么不能在小于8的时候立刻转会来呢?

如果8的时候转红黑树,小于8就立刻转回来,那么这就可能导致频繁的转换,所以要选择一个小于8的临界值,但又不能是7,从前面的泊松分布可以看到,当红黑树节点小于6时,它所带来的优势其实已经没有那么大了,就不足以抵消由于红黑树维护节点所带来的额外开销,此时转换回来能节省空间和时间。

四、红黑树的双向链表

看过源码的同学会发现,HashMap红黑树的数据结构中,不仅有常见的parent、left、right节点,还有一个nextprev节点。这说明它不仅是一个红黑树,还是一个双向链表。

这样做的原因:

红黑树会记录树化之前的链表结构,这样当红黑树退化成链表的时候,就可以直接按照链表重新链接的方式进行。

不过可能有人会问,那不是需要一个next节点就行了,为啥还要prev呢?

如果删除的是原始链表的中间节点,只靠next是无法将原始的链表重新连接的,所以就需要有prev节点,找到上一个节点,重新连接。

 End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。 

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

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

相关文章

网络安全 - ARP 欺骗原理+实验

APR 欺骗 什么是 APR 为什么要用 APR A P R \color{cyan}{APR} APR&#xff08;Address Resolution Protocol&#xff09;即地址解析协议&#xff0c;负责将某个 IP 地址解析成对应的 MAC 地址。 在网络通信过程中会使用到这两种地址&#xff0c;逻辑 IP 地址和物理 MAC 地址&…

Stable Diffusion 3 即将开源 引AI界巨大震动;马斯克考虑打造X Phone并威胁禁用苹果设备

&#x1f989; AI新闻 &#x1f680; Stable Diffusion 3 即将开源 引AI界巨大震动 摘要&#xff1a;Stable Diffusion 3有望在明天开源&#xff0c;带来革命性的AI生成图像技术。该版本采用MMDiT全新架构&#xff0c;可能彻底改变AI图像生成格局。尽管Stability AI公司面临财…

国产化替代及现有程序优化

数据库优化 月中、月末程序用的多 数据库慢查询sql 较多&#xff0c;增加数据库服务器内存 现在16G通过内存数据库&#xff0c;中间件缓解数据库压力&#xff0c;热点数据通过内存数据存储客户端不直接连 数据库Sql语句 优化 避免过多join数据库读写分离&#xff0c;甚至分布…

《软件定义安全》之八:软件定义安全案例

第8章 软件定义安全案例 1.国外案例 1.1 Fortinet&#xff1a;传统安全公司的软件定义方案 Fortinet的软件定义安全架构强调与数据中心的结合&#xff0c;旨在将安全转型为软件定义的模式&#xff0c;使安全运维能够与数据中心的其他部分一样灵活、弹性。在Fortinet看来&…

cdh zookeeper报错 Canary 测试建立与 ZooKeeper 服务的连接或者客户端会话失败。

我一直纳闷这个是什么问题&#xff0c;搜索了半天没有结果&#xff0c;因为别人没有遇到过。后面我重新搭建了另一套cdh&#xff0c;然后看了一下默认的配置&#xff0c;然后更新上去才发现的。 这里面的clientPortAddress不要手动设置端口号。 别勾选通信验证 不要开启TLS/SS…

政安晨【零基础玩转各类开源AI项目】解析开源:gradio:在Python中构建机器学习Web应用

目录 下载项目 快速开始 Gradio能做什么&#xff1f; Hello, World Interface 类 组件属性 多输入和输出组件 一个图像示例 Blocks: 更加灵活且可控 你好, Blocks 更多复杂性 尝试 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏…

学习笔记——网络管理与运维——概述(背景)

一、背景 1、计算机时代的演进 1964年IBM公司花费50亿美金开发出了IBM SYSTEM/360大型机&#xff0c;开始了大型机的历史。大型机通常采用集中式体系架构&#xff0c;这种架构的优势之一是其出色的I/O处理能力&#xff0c;因而最适合处理大规模事务数据。与PC生态系统比较&…

Java 截压缩包(zip包),解析xml文件-工具类

Java 截压缩包&#xff08;zip包&#xff09;&#xff0c;解析xml文件-工具类 技术&#xff1a;在Java中&#xff0c;使用Java自带的java.util.zip.ZipFile包 代码示例如下&#xff1a; 注1&#xff1a;在下面的代码中&#xff0c;zipFilePath替换为要解压缩的.zip文件的实际…

编译原理-程序设计语言的设计

机器语言:二进制、机器相关 汇编语言:助记符、机器相关(机器语言与汇编语言都是低级语言) 高级语言:接近自然语言、机器无关 把一种语言程序编写的转换成完全等效的另一种语言编写的程序为翻译。 编译程序: 源程序语言是高级语言,目标程序语言是汇编语言或机器语言之类…

*MySQL事务

目录 一、概念理解&#xff1a; 二、回滚&#xff08;rollback&#xff09; 三、事务的四大特性&#xff08;ACID&#xff09;&#xff1a; 1&#xff09;原子性&#xff08;Atomicity&#xff09; 2&#xff09;一致性&#xff08;Consistency&#xff09; 3&#xff09;隔…

k8s nginx.conf配置文件配置

无状态nginx配置nginx.conf覆盖容器配置nginx.conf 代码&#xff1a;events {worker_connections 1024; }http {include /etc/nginx/mime.types;default_type application/octet-stream;log_format main $remote_addr - $remote_user [$time_local] "$request&q…

服务器数据恢复—vxfs文件系统元数据被破坏的数据恢复案例

服务器存储数据恢复环境&#xff1a; 某品牌MSA2000服务器存储中有一组由8块SAS硬盘组建的raid5磁盘阵列&#xff0c;其中包含一块热备盘。分配了6个LUN&#xff0c;均分配给HP-Unix小机使用。磁盘分区由LVM进行管理&#xff0c;存放的数据主要为Oracle数据库及OA服务端。 服务…

iOS--oc对象,类,和元类本质

iOS--oc对象&#xff0c;类&#xff0c;和元类本质 前言实例对象的具体结构自定义类对象的结构继承关系 类信息的存放对isa、superclass总结 前言 最近在学习runtime的过程中&#xff0c;发现其中消息发送-动态方法解析-消息转发中涉及到了大量的类与对象的底层知识&#xff0…

【LeetCode滑动窗口算法】长度最小的子数组 难度:中等

我们先看一下题目描述&#xff1a; 解法一&#xff1a;暴力枚举 时间复杂度&#xff1a;o(n^3) class Solution { public:int minSubArrayLen(int target, vector<int>& nums){int i 0, j 0;vector<int> v;for (;i < nums.size();i){int sum nums[i];fo…

从ES的JVM配置起步思考JVM常见参数优化

目录 一、真实查看参数 &#xff08;一&#xff09;-XX:PrintCommandLineFlags &#xff08;二&#xff09;-XX:PrintFlagsFinal 二、堆空间的配置 &#xff08;一&#xff09;默认配置 &#xff08;二&#xff09;配置Elasticsearch堆内存时&#xff0c;将初始大小设置为…

.net8 blazor auto模式很爽(二)用.net8创建Blazor自动模式项目

在vs2022中创建新项目&#xff0c;在搜索框里输入blazor&#xff0c;选择blazor web app 在其他信息里框架选.net8&#xff0c;模式选择auto,点创建。 我们可以看到&#xff0c;vs自动创建了两个项目。一个叫BlazorApp1&#xff0c;另外一个叫BlazorApp1.Client。没有Client就…

链表题目之指定区间处理

前言 链表中有一些题目是需要知道并且记住对应的技巧的&#xff0c;有一些题目就是基本的链表技巧手动模拟推演注意细节等。 对于需要知道并且记住对应技巧的题目会有专门的一栏进行讲解&#xff0c;此类题目主要有&#xff1a;相交链表、环形链表、回文链表等&#xff0c;这些…

网络地图的发展历程

位置以及我们与位置的互动方式已在我们的生活中无处不在。我们的网络地图技术发展到今天这一步&#xff0c;涉及一系列个人、公司和想法&#xff0c;这些最终塑造了我们与世界的互动方式。这篇文章能帮助您了解我们是如何一步步走到今天的。即网络地图的发展历史! 制图学的简要…

笨蛋学算法之LeetCodeHot100_4_移动零(Java)

package com.lsy.leetcodehot100;public class _Hot4_移动零 {public static int[] moveZeroes(int[] nums){//判断数组是否为nullif(numsnull && nums.length0){return null;}/*** 初始化两个指针 i 和 noZero&#xff0c;其中 i 用于遍历数组&#xff0c;noZero 用于…

【讲解下Stylus入门方法】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…