HashMap会用就行了?一文解决HashMap的底层问题

前言

        我们的手机通讯录之所以能快速定位到特定联系人,就是因为它运用了HashMap底层的原理。手机通讯录将每个联系人的姓名作为键,电话号码作为对应的值,通过这个键值对的方式实现了快速的数据定位和获取。就像你通过关键字快速找到对应的联系人一样,HashMap通过键的映射,为我们提供了高效的数据管理方式。通过手机通讯录这个生活化的例子,我们能更直观地理解HashMap底层的原理。在接下来的文章中,我们将深入剖析HashMap的内部机制,解开它背后的技术奥秘。让我们一起揭开手机通讯录这个神奇的密码保险柜,探索HashMap的魅力吧!

HashMap底层结构

HashMap的实现结构主要有三个:

  • 二叉树

  • 红黑树

  • 散列表

红黑树

红黑树(Red Black Tree):也是一种自平衡的二叉搜索树(BST),之前叫做平衡二叉B树(Symmetric Binary B-Tree),CRUD操作的时间复杂度为:O(log n)

  • 性质1:节点要么是红色,要么是黑色

  • 性质2:根节点是黑色

  • 性质3:叶子节点都是黑色的空节点

  • 性质4:红黑树中红色节点的子节点都是黑色

  • 性质5:从任一节点到叶子节点的所有路径都包含相同数目的黑色节点(高度差不能超过1)

在添加或删除节点的时候,如果不符合这些性质会发生旋转,以达到所有的性质

散列表(哈希表)

在散列表中,数据元素通常被存储在数组中。每个数据元素都有一个对应的关键字,通过对关键字进行哈希(散列)运算,得到一个数组索引,将数据元素保存在该索引位置上。当需要访问一个数据元素时,通过相同的哈希运算找到对应的索引即可快速地获取到数据。

其实本质上就是个数组的理念,只不过说现在是将数组的下标可以设置为了一个key而已,对于客户端来说是通过key来找到该元素,实际上底层是对该key进行散列计算,得到底层数组的该元素的下标而已。

哈希冲突

由于散列算法的结果是无限的,是由于哈希算法被计算的数据是无限的,而计算后的结果范围有限,所以总会存在不同的数据经过计算后得到的值相同,这就是哈希冲突。

解决方法:

  • 开放定址法,也称为线性探测法,就是从发生冲突的那个位置开始,按照一定的次序从 hash 表中找到一个空闲的位置,然后把发生冲突的元素存入到这个空闲位置中。ThreadLocal 就用到了线性探测法来解决 hash 冲突的。

  • 链式寻址法,这是一种非常常见的方法,简单理解就是把存在 hash 冲突的 key,以单向链表的方式来存储,比如 HashMap 就是采用链式寻址法来实现的。

  • 再 hash 法,就是当通过某个 hash 函数计算的 key 存在冲突时,再用另外一个 hash 函数对这个 key 做 hash,一直运算直到不再产生冲突。这种方式会增加计算时间,性能影响较大。

  • 建立公共溢出区, 就是把 hash 表分为基本表和溢出表两个部分,凡事存在冲突的元素,一律放入到溢出表中。

HashMap 在 JDK1.8 版本中,通过链式寻址法+红黑树的方式来解决 hash 冲突问题,其中红黑树是为了优化 Hash 表链表过长导致时间复杂度增加的问题。当链表长度大于 8 并且 hash 表的容量大于 64 的时候,再向链表中添加元素就会触发转化。将链表法中的链表改造红黑树还有一个非常重要的原因,可以防止DDos攻击

  • DDoS(Distributed Denial of Service)攻击是一种网络攻击方式,目的是通过同时从多个计算机向目标服务器发送大量的请求,以致使目标服务器无法正常工作或响应正常用户的请求。采用红黑树的结构,可以避免二叉树斜树问题的产生,容纳更多的数据,避免服务瘫痪


拓展:红黑树和AVL树的区别 

红黑树特点

  • 规则1: 每个节点不是黑色就是红色

  • 规则2:根节点为黑色

  • 规则3:红色节点的父节点和子节点不能为红色

  • 规则4:所有的叶子节点都是黑色(空节点视为叶子节点NIL)

  • 规则5:每个节点到叶子节点的每个路径黑色节点的个数都相等。

平衡二叉树特点

  • 规则1:每个节点最多只有两个子节点(二叉)

  • 规则2:每个节点的值比它的左子树所有的节点大,比它的右子树所有节点小(有序)

  • 规则3:每个节点左子树的高度与右子树高度之差的绝对值不超过1

二者比较

红黑树和AVL树都是自平衡二叉搜索树,它们的平衡维护是为了保证树的高度不至于太大,从而使树的基本操作的时间复杂度始终保持在 O(log n) 的级别内。

插入删除性能
  • AVL树的平衡维护是通过每个节点的左子树高度和右子树高度之差(即平衡因子)是否为 -1、0、1 来实现的,这种平衡维护要求非常严格。当一个节点的平衡因子超出了 -1、0、1 的范围时,就需要通过旋转操作来调整树的结构。因为AVL树是严格平衡的,所以在插入或删除节点时,可能需要进行多次旋转操作才能将树保持在平衡状态,并且还需要更新每个节点的平衡信息,导致时间代价较高。

  • 而红黑树则采用更加灵活的平衡维护方式。它将节点分为红色和黑色并通过特定的规则来保证树的平衡。与 AVL 树不同,红黑树允许节点之间的高度差超过 1,只需要改变少量节点的颜色信息不用旋转,就能达到平衡,并可能进行较少的旋转,但它通过保证从根节点到叶节点的任意路径上黑色节点的数量相同来避免树的高度过大。因为红黑树的平衡维护更加灵活,所以在插入或删除节点时,需要调整的次数相对较少,导致时间代价较低。

空间成本
  • 红黑树相对于AVL树需要额外存储颜色信息、指向父节点的指针,以及额外的1位(或1字节)空间来表示颜色。相比之下,AVL树相对简单,不需要额外存储颜色信息,只需要存储平衡因子。


HashMap的实现原理

 

HashMap解决哈希冲突在jdk1.7和jdk1.8的区别

  • JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

  • jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8) 时并且数组长度达到64时,将链表转化为红黑树,以减少搜索时间。扩容 resize( ) 时,红黑树拆分成的树的结点数小于等于临界值6个,则退化成链表

HashMap的put方法的具体流程

  • HashMap是 懒惰加载 ,在创建对象时并没有初始化数组

  • 在无参的构造函数中,设置了默认的加载因子是 0.75

  1. 判断键值对数组table是否为空或为null,否则执行resize()进行扩容(初始化)

  2. 根据键值key计算hash值得到数组索引

  3. 判断table[i]==null,条件成立,直接新建节点添加

  4. 如果table[i]==null ,不成立

    1. 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value

    2. 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对

    3. 遍历table[i],链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操 作,遍历过程中若发现key已经存在直接覆盖value

  5. 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold(数组长度*0.75),如果超过,进行扩容。


HashMap源码分析-扩容


HashMap的寻址算法

一旦获得了键的哈希码,HashMap会使用一个寻址算法来确定键值对在Hash表中的存储位置。这个寻址算法被称为散列冲突解决方法。

HashMap允许存储null作为键和值。当你插入键为null时,HashMap会将它们放入内部数组的0下标位置

具体的hash值计算方式为

  1. key取hash值为h

  2. 对h二进制向右逻辑移动 16 位,通过这样的移位操作,可以使得不同的对象的高16位也参与到哈希计算中,从而在一定程度上减少哈希冲突的可能性。

  3. 将h与步骤2的位移结果进行异或运算,得到最终的哈希值。


为何HashMap的数组长度一定是2的次幂?

  • 计算索引时效率更高:如果是 2 的 n 次幂可以使用位与运算代替取模

  • 扩容时重新计算索引效率更高: hash & oldCap == 0 的元素留在原来位置 ,否则新位置 = 旧位置 + oldCap


HashMap多线程下死循环问题

        HashMap死循环问题是JDK1.7之前存在问题,主要源*于HashMap的自身的工作机制和并发处理导致*的问题,而对于JDK1.8后,官方就彻底解决了这个问题,对于死循环问题,我们首先了解一下HashMap数据插入原理

JDK1.7的HashMap头插法

        在Java的HashMap中,put()操作采用的是“头插法”,也就是把新元素插入到链表头部。如有相同的key值插入,会覆盖旧元素。如果新元素的key值在HashMap中不存在,则会新建一个节点并放在链表头部。如果此时桶数组中对应位置已经有了元素,那么新插入的元素会作为该元素的前驱。

img

导致死循环的原因

阶段一:

多线程下多个线程同时在扩容临界点进行插入操作,同时开始进行扩容

阶段二:

        多个线程同时进行扩容,扩容前首先需要获取结尾元素的下一个节点信息,以便使用头插法进行扩容。然后有部分线程时间片用完,在获取节点信息后就就行休眠了,还没开始进行头插扩容。有部分线程直接进行扩容操作,在扩容操作过程种的节点结构重组消息休眠线程不知道。

img

阶段三:

第二阶段进行扩容操作后,节点结构重组后,休眠的线程拿着旧的节点消息进行二次头插,致使死循环

img

总结

img


为什么重写equals()就一定要重写hashCode()方法?

        在Java中,equals()方法用于比较两个对象的内容是否相等,而hashCode()方法用于计算对象的哈希码(hash code)。哈希码是一个整数,用于快速定位对象在哈希表等数据结构中的存储位置。

        当我们将对象存储在基于哈希的数据结构中,例如HashSet、HashMap等,它们会使用hashCode()方法来确定对象在内部数据结构中的存储位置。当我们要查找、插入或删除对象时,会首先计算对象的哈希码,然后在相应的位置进行操作。

        而equals()方法则用于在发生哈希碰撞(相同哈希码,不同对象)的情况下,进一步比较对象的内容是否相等。哈希碰撞是指不同的对象具有相同的hashCode()值,在哈希表等数据结构中可能发生冲突。

        如果两个对象被equals()方法判断为相等,那么它们的hashCode()方法应该返回相同的值。这是因为在哈希表等数据结构中,equals()方法相等的对象应该存储在相同的位置,而hashCode()方法相等的对象应该具有相同的哈希码。

        如果我们只重写equals()方法而不重写hashCode()方法,会导致在哈希表等数据结构中出现问题。查找等操作可能无法正确地定位到对象,甚至会导致对象无法正常存取。


为什么使用红黑树而不使用AVL树?

  • 插入、删除操作的代价较低:红黑树的调整策略对于插入或删除的代价相对较低,而 AVL 树的严格平衡策略可能需要更多的操作来保持平衡,从而导致时间代价更高。

  • 更好的插入、删除性能:当需要插入和删除节点时,红黑树的结构调整代价相对较小,因此相对来说插入和删除操作的性能更好


在解决 hash 冲突的时候,为什么选择先用链表,再转红黑树?

        因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。当元素小于 8 个的时候,链表结构可以保证查询性能。当元素大于 8 个的时候, 红黑树搜索时间复杂度是 O(logn),而链表是 O(n),此时需要红黑树来加快查询速度,但是插入和删除节点的效率变慢了。如果一开始就用红黑树结构,元素太少,插入和删除节点的效率又比较慢,浪费性能。

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

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

相关文章

想要成为CSS大师?这些技巧是你必须知道的!

前言 CSS 是网页设计中不可或缺的一部分,掌握一些实用的 CSS 技巧,可以让你在设计中展现出更多的创意和个性。本文将介绍一些 CSS 技巧,帮助你提升自己的技能,成为一个真正的 CSS 大师。 1. 改变 input 自动填充的背景颜色 这段 …

Apache Airflow (十一) :HiveOperator及调度HQL

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹…

Docker网络详细说明

Docker网络 docker不启动,默认网络情况 ipconfig----------ens33、lo、virbr0 在CentOS7的安装过程中如果有选择相关虚拟化的的服务安装系统后,启动网卡时会发现有一个以网桥连接的私网地址的virbr0网卡(virbr0网卡:它还有一个固定的默认I…

Nginx - 本机读取服务器图像、视频

目录 一.引言 二.安装 Nginx 1.安装 By apt 2.安装 By 官网 三.配置 Nginx 1.Linux 机器配置 2.重启 Nginx 服务 3.本机查看机器文件 四.总结 一.引言 前面介绍了 GFP-GAN、PNG2GIF、GIF2PNG 等操作,我们生成的 video、gif、png 等形式的文件都存储在 lin…

无菌药品生产(A级区域)--气流流型测试可视化烟雾试验详细介绍

技术背景 无菌药品是指法定药品标准中列有无菌检测项目的制剂和原料药,包括注射剂、眼用制剂、无菌软膏剂、无菌混悬剂等。目前工程中,以注射剂产品为主的厂房占据了很大的比重。 无菌药品生产质量风险管理中,人员及其活动被视为重大的污染…

php 时区查看和设置

php的时区&#xff0c;关系到相关时间函数的结果 其他相关&#xff1a; linux时区设置&#xff1a;链接 pgsql时区设置&#xff1a; 一、查看可以用的时区列表 新建一个php文件&#xff0c;输入下面程序即可 <?php echo "<pre>"; var_dump(timezone_id…

【C++】pow函数实现的伽马变换详解和示例

本文通过原理和示例对伽马变换进行详解&#xff0c;并通过改变变换系数展示不同的效果&#xff0c;以帮助大家理解和使用。 原理 伽马变换是一种用于图像增强的技术&#xff0c;它可以用来提高或降低图像的对比度&#xff0c;常用于医学图像处理和计算机视觉等领域。伽马变换…

px、em、rem、百分比的区别

文章目录 1. 单位类型与相对基准2. 相对长度1.em2.rem3.%百分比4.vw 和 vh5.vmin 和 vmax6.vm7.ch8. ex 3. 总结 1. 单位类型与相对基准 单位名称 单位类型&#xff08;相对/绝对&#xff09; 相对基准 px 相对 屏幕像素缩放后的尺寸 百分比 相对 font-size相对于继承&#xf…

转录组学习第三弹-下载SRR数据并转成fastq

下载数据 前面已经安装好了需要的软件&#xff0c;那么我们现在需要下载我们练习需要用到的sra数据。从 SRA 数据库下载数据有多种方法。可以用ascp快速的来下载 sra 文件&#xff0c;也可以用wget或curl等传统命令从 FTP 服务器上下载 sra 文件。另外sra-tools的prefetch也支…

麒麟KYSEC使用方法01--开启及关闭kysec

原文链接&#xff1a;麒麟KYSEC使用方法01–开启及关闭kysec hello&#xff0c;大家好啊&#xff0c;今天给大家带来麒麟KYLINOS的kysec使用方法系列文章第一篇内容----使用命令开启及关闭kysec&#xff0c;kysec有三种模式&#xff0c;disable/enable/softmode&#xff0c;今天…

算法--- 叶子相似的树

题目 请考虑一棵二叉树上所有的叶子&#xff0c;这些叶子的值按从左到右的顺序排列形成一个 叶值序列 。 举个例子&#xff0c;如上图所示&#xff0c;给定一棵叶值序列为 (6, 7, 4, 9, 8) 的树。 如果有两棵二叉树的叶值序列是相同&#xff0c;那么我们就认为它们是 叶相似…

作为测试你连K8S都不懂,你还有脸说自己是测试?

kubernetes&#xff0c;简称 K8s&#xff0c;是用 8 代替中间 8 个字符 “ubernete” 而成的缩写&#xff0c;是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应用&#xff0c;Kubernetes 的目标是让部署容器化的应用简单并且高效(powerful)&#xff0c;Kuberne…