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.mfbz.cn/a/167885.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue动态配置路由

文章目录 前言定义项目页面格式一、vite 配置动态路由新建 /router/utils.ts引入 /router/utils.ts 二、webpack 配置动态路由总结如有启发,可点赞收藏哟~ 前言 项目中动态配置路由可以减少路由配置时间,并可减少配置路由出现的一些奇奇怪怪的问题 路由…

你学了Python之后让你成为行业卷王,升职加薪更有优势

都说Python能够实现自动化,那么Python具体能应用在哪些地方?哪些岗位学了Python更有优势?今天我们来看看一些大神将Python应用的出神入化的成果。 在这之前,先跟为大家分享个真实的故事。我朋友小宇前段时间为了一个品牌设计的大项目,想方案…

Elasticsearch 和 LangChain 合作开发可用于生产的 RAG 模板

作者:Aditya Tripathi 在过去的几个月里,我们一直与 LangChain 团队密切合作,他们在推出 LangServe 和 LangChain 模板方面取得了进展! LangChain Templates 是一组用于构建生产质量的生成式 AI 应用程序的参考架构。 你可以在此处…

QMI8658A Datasheet Rev A-勘误表

QMI8658A Datasheet Rev A-勘误表 1. Reset Register2. CTRL9 Command List3. Temp Sensor Output 1. Reset Register 在5.9章节 和 7.4 章节对复位操作的写入数据,有笔误 正确的数据是: 0xB0 2. CTRL9 Command List 在 5.10.2 章节 Table 28. List…

汇编-loop循环指令

LOOP指令是根据ECX计数器循环,将语句块重复执行特定次数。 ECX自动作为计数器, 每重复循环一次就递减1。 语法如下所示: 循环目的地址必须在距离当前位置计数器的-128到127字节范围内 LOOP指令的执行有两个步骤: 第一步&…

SpringBoot的启动流程

一、SpringBoot是什么? springboot是依赖于spring的,比起spring,除了拥有spring的全部功能以外,springboot无需繁琐的xml配置,这取决于它自身强大的自动装配功能;并且自身已嵌入Tomcat、Jetty等web容器&am…

GreatSQL社区与Amazon、Facebook、Tencent共同被MySQL致谢

一、来自MySQL官方的感谢 在 2023-10-25 MySQL 官方发布的 8.2 版本 Release Notes 中,GreatSQL 社区核心开发者 Richard Dang 和 Hao Lu ,分别收到了来自 MySQL 官方的贡献感谢,与Amazon、Facebook(Meta)、Tencent等一并出现在感谢清单中。…

2023年电子工程师大会暨第三届社区年度颁奖活动--【其利天下技术】

华秋电子发烧友将于2023年11月23日在深圳举办一场盛大的技术交流活动,即“2023年电子工程师大会暨第三届社区年度颁奖活动”。本次活动邀请了各大高校教授、企业高管、行业专家和电子工程师们齐聚一堂,围绕“开源硬件”、“OpenHarmony RISC-V”、“工程…

【技术指南资料】编码器与正交译码器

我想提出一个关于PicoScope7新的译码器功能讨论。它已经推出一段时间,但你可能不知道这在汽车领域是扮演相当重要的角色。 正交译码器被用在转子位置传感器来转换关于旋转轴角度及方向的信息。 举例来说,它在电机上采用一对二进制的信号型式。 这种传感器…

C#的类型转换

目录 一、简介二、基本类型转换1.整数类型转换1.隐式转换2.显式转换 2.浮点类型转换1.隐式转换2.显式转换 3.字符类型转换1.字符到整数的转换2.整数到字符的转换 4.布尔类型转换1.布尔到整数的转换2.整数到布尔的转换 三、隐式转换和显式转换四、装箱和拆箱五、自定义类型转换六…

详解SwinIR的论文和代码(SwinIR: Image Restoration Using Swin Transformer)

paper:https://arxiv.org/abs/2108.10257 code:https://github.com/JingyunLiang/SwinIR 目录 1. Swin Transformer layers1.1 局部注意力1.2 移动窗口机制1.3 关键代码理解 2. 整体网络结构2.1 浅层特征提取2.2 深层特征提取2.3 图像重建 3.总结 SwinI…

BUUCTF 秘密文件 1

BUUCTF:https://buuoj.cn/challenges 题目描述: 深夜里,Hack偷偷的潜入了某公司的内网,趁着深夜偷走了公司的秘密文件,公司的网络管理员通过通过监控工具成功的截取Hack入侵时数据流量,但是却无法分析出Hack到底偷走…

Azure 机器学习 - 搜索中的检索增强 (RAG)

目录 一、Azure AI 信息检索系统介绍二、采用 Azure AI 搜索的 RAG 方法三、适合 Azure AI 搜索的自定义 RAG 模式四、Azure AI 搜索中的可搜索内容五、Azure AI 搜索中的内容检索构建查询响应按相关性排名适用于 RAG 方案的 Azure AI 搜索查询的示例代码 六、集成代码和 LLM七…

【MySQL】_JDBC

目录 1. JDBC原理 2. 导入JDBC驱动包 3. 编写JDBC代码实现Insert 3.1 创建并初始化一个数据源 3.2 和数据库服务器建立连接 3.3 构造SQL语句 3.4 执行SQL语句 3.5 释放必要的资源 4. JDBC代码的优化 4.1 从控制台输入 4.2 避免SQL注入的SQL语句 5. 编写JDBC代码实现…

深入Ansible

1.什么是ansible ansible是新出现的自动化运维工具,基于Python开发,集合了众多运维工具(puppet、chef、func、fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。 ansible是基于 paramiko 开发的…

11月20日星期一今日早报简报微语报早读

11月20日星期一,农历十月初八,早报微语早读。 1、T1以3-0横扫WBG,拿下S13冠军!Faker豪取第4冠; 2、天舟七号货运飞船已运抵文昌发射场,将于明年初发射; 3、“中韩之战”球票已经售罄&#xf…

没收到Win11 23H2正式版的推送怎么升级到23H2

没收到Win11 23H2正式版的推送怎么升级到23H2?用户反映自己没有收到Win11 23H2正式版的更新推送,又想升级为23H2版本。接下来小编给大家详细介绍不同的升级方法,帮助更多的用户完成Win11 23H2系统的更新,升级后就能体验到Win11 23…

解锁安全与信任的双重礼遇!JoySSL证书买二送一,买三送二

JoySSL是业内领先的SSL证书提供商,致力于为网站提供最高水平的安全性。通过使用JoySSL证书,您的网站将获得强大的加密保护,确保用户的敏感信息在传输过程中得到安全加密,有效地抵御各种网络威胁。 为何选择JoySSL证书&#xff1f…

解决龙芯loongarch64服务器编译安装Python后yum命令无法使用的问题“no module named ‘dnf‘”

引言 在使用Linux系统时,我们经常会使用yum来管理软件包。然而,有时候我们可能会遇到yum不可用的情况,其中一个原因就是Python的问题。本文将介绍Python对yum可用性的影响,并提供解决方案。 问题引发 正常情况下,安装linux系统后,yum命令是可用状态,升级Python版本后,…

pyqt5切换到pyqt6遇到问题

pyqt5切换到pyqt6变更点 FramelessWindowHint Qt.FramelessWindowHint Qt.WindowType.FramelessWindowHint globalPos event.globalPos() event.globalPosition() LeftButton Qt.LeftButton Qt.MouseButton.LeftButton StrongFocus Qt.StrongFocus Qt.FocusPolicy.Stro…