首页 > 其他分享 >一文彻底搞懂HashMap

一文彻底搞懂HashMap

时间:2024-03-23 18:33:07浏览次数:29  
标签:黑树 HashMap 阈值 性能 链表 数组 搞懂 一文

文章目录

1. 数据结构

JDK 7 中的 HashMap 使⽤的是数组 + 链表的实现⽅式,即拉链法。当发生哈希冲突时,即多个键映射到同一个数组索引位置时,HashMap会将这些键值对存储在同一个数组位置上的链表中,并通过链表将它们串联起来。这种实现方式在处理冲突时需要顺序遍历链表,当链表长度较长时,查找效率会降低,导致性能下降。

为了解决这个问题,JDK 8引入了红黑树(Red-Black Tree)来替代链表,以提高在链表长度较长时的查找性能。具体做法是,在发生哈希冲突后,如果链表长度超过一定阈值(默认为8),HashMap会将链表转换为红黑树,这样就可以在O(log n)的时间复杂度内完成查找操作,大大提高了查找性能。这种使用红黑树替代链表的结构被称为树化桶(Tree Bins)。在红黑树的实现中,HashMap保持了与链表相同的API,并且在需要的情况下能够在链表和红黑树之间进行平稳的转换,从而在不同情况下都能够保持较好的性能表现。

JDK 8 中 HashMap 的数据结构是数组+链表+红黑树
在这里插入图片描述

2. 扩容机制

HashMap 的初始容量是 16,随着元素的不断添加,HashMap 的容量(也就是数组大小)可能不足,于是就需要进行扩容,阈值是capacity * loadFactor,capacity 为容量,loadFactor 为负载因子,默认为 0.75。

扩容后的数组大小是原来的 2 倍,然后把原来的元素重新计算哈希值,放到新的数组中。

HashMap 的性能大大依赖于这三个重要的参数:初始容量(initial capacity)、加载因子(load factor,或者叫负载因子,默认为 0.75)、阈值(threshold)。

当HashMap中的元素数量达到了负载因子与当前容量的乘积时,就会触发扩容操作。

具体来说,HashMap在扩容时会创建一个新的容量是原容量两倍的数组,并将原来数组中的元素重新分配到新的数组中。重新分配的过程包括以下几个步骤:

  • 创建一个新的容量是原容量两倍的数组。
  • 遍历原数组中的每个元素,将其重新计算哈希值,然后根据新数组的长度取模得到新的索引位置。
  • 如果新索引位置上已经有元素存在,采用链表或红黑树的方式处理冲突,将新元素插入到对应位置的链表或红黑树中。
  • 重复以上步骤,直到原数组中的所有元素都被重新分配到新数组中。
  • 在重新分配元素的过程中,由于元素数量增加,可能会发生哈希冲突,需要对冲突进行处理。在 JDK 8 中,如果链表长度超过一定阈值(默认为8),则会将链表转换为红黑树,以提高查找性能。

扩容操作是一个相对耗时的操作,因为它涉及到重新计算哈希值、重新分配元素等操作。为了减少扩容的频率,可以通过调整负载因子来控制HashMap的容量与元素数量的比例,以减少扩容操作的发生频率,从而提高HashMap的性能。

3. 常问问题

3.1 HashMap为什么要树化

在 JDK 8 中,HashMap 在处理哈希冲突时引入了树化机制,即将链表转换为红黑树,这是为了解决在极端情况下链表过长导致的性能问题。

当哈希表中的链表长度超过一定阈值(默认为8)时,HashMap 会将该链表转换为红黑树。这是因为在链表长度过长时,链表的查找效率会变得很低,甚至会退化为 O(n) 的线性查找时间复杂度。而红黑树的平均查找时间复杂度为 O(log n),远远优于链表。因此,将链表转换为红黑树可以提高 HashMap 的查找性能,特别是在极端情况下,即使在大规模数据集下也能保持较稳定的性能。

树化机制的引入解决了 JDK 7 中 HashMap 的链表退化问题,提高了 HashMap 在处理大规模数据时的性能和稳定性。

3.2 链表中转红黑树的阈值为什么设为8

将链表转换为红黑树的阈值在 JDK 中是一个经过多方面考量和测试确定的参数。设定此阈值的目的是在权衡时间和空间开销的基础上,以提高在大型数据集中的 HashMap 性能。

选择阈值为8的原因主要有以下几点:

  • 性能平衡: 较小的阈值意味着更频繁地将链表转换为树,这可能会增加一些性能开销,例如树结构的维护和节点的增加。较大的阈值意味着更少的链表转换,但是当链表长度超过阈值时,链表的性能会急剧下降。因此,阈值的选择需要在时间和空间开销之间进行平衡,以达到最佳性能。

  • 实践经验: 阈值的选择可能是基于大量实际数据集的测试和分析。通过观察实际数据集中链表的长度分布,可以确定一个合适的阈值,以便在绝大多数情况下都能够有效地提高性能。

  • 性能优化: 较小的阈值可以更早地将链表转换为树,从而减少链表的长度,提高树的性能。同时,较小的树在插入、删除等操作时可能具有更高的效率,因为树的平衡性更容易维持。

  • 内存开销: 将链表转换为树会增加一些额外的内存开销,包括树节点的存储和维护。选择较小的阈值可以控制树的大小,从而限制额外的内存开销。

总的来说,将链表转换为红黑树的阈值为8是在性能、内存开销和实践经验的基础上做出的权衡选择,旨在在大多数情况下提供最佳的性能和稳定性。

标签:黑树,HashMap,阈值,性能,链表,数组,搞懂,一文
From: https://blog.csdn.net/weixin_44772566/article/details/136972092

相关文章

  • 一文聊透 Flink 的 Temporal Join
    博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行,点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,京东购书链接:https://item.jd.com/12677623.html,扫描左侧二维码进入京东手......
  • 一文彻底搞懂背包问题
    文章目录1.01背包问题2.完全背包问题3.多重背包问题01背包、完全背包和多重背包问题都属于经典的背包问题,这些问题都可以通过动态规划来解决,它们在动态规划中有着不同的特点和解法,其中状态转移方程是解决这些问题的核心。通过填表的方式,逐步求解最优解,最终得到问题......
  • YOLOv8改进 | 注意力篇 | 一文带你改进GAM、CBAM、CA、ECA等通道注意力机制和多头注意
    一、本文介绍这篇文章给大家带来的改进机制是一个汇总篇,包含一些简单的注意力机制,本来一直不想发这些内容的(网上教程太多了,发出来增加文章数量也没什么意义),但是群内的读者很多都问我这些机制所以单独出一期视频来汇总一些比较简单的注意力机制添加的方法和使用教程,本文的内容......
  • ConcurrentHashMap底层详解
    ConcurrentHashMap是线程安全且高效的HashMap。一、使用原因在并发编程中使用HashMap可能导致程序死循环。而使用线程安全的HashTable效率又非常低下,基于此产生了ConcurrentHashMap。1.线程不安全的HashMap在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率......
  • 一文弄懂浏览器的重排(回流)与重绘
    浏览器渲染过程在浏览器从服务器下载到资源后解析HTML形成DOM树,解析CSS形成CSSOM树。渲染树:将DOM树和CSSOM树结合创建render树。Layout:根据渲染树进行布局,得到节点的几何信息(位置大小)。Painting:布局完成后浏览器根据结果和渲染树,将具体的像素绘制出来。重......
  • 一文搞懂idea中的根目录和路径(以Mybatis为例)
    1.根目录概念:1.1项目根目录(ProjectRoot)项目根目录是你在文件系统中为整个项目选择的顶层目录。它通常包含了项目的所有内容,包括源代码、构建配置文件、文档、测试文件等。在版本控制系统中(如Git),项目根目录通常是仓库的根目录。1.2内容根目录(ContentRoot)在IntelliJ......
  • 一文彻底搞懂令人疑惑的Java和JDK的版本命名!
    你对Java的版本号以及JDK的命名真正清楚嘛?比如:Java8JavaSE8.0JDK1.8……知道这些是怎么回事嘛?知道还有个Java2的说法嘛?知道还有以下说法嘛?J2SE1.3J2SE1.4……现在已经6月份了,到了9月份,一个新的长期支持版本,Java17就要发布了,啥?Java版本都到17了?不不不,我一直在用JDK......
  • 一文了解Python中的运算
    Python的运算符和其他语言类似数学运算>>>print 1+9        # 加法>>>print 1.3-4      # 减法>>>print 3*5        # 乘法>>>print 4.5/1.5    # 除法>>>print 3**2       # 乘方     >>>print 10%3      ......
  • 一文搞懂IP
    IP1.基本介绍2.IP地址定义3.IP地址分类4.子网掩码5.全局地址与私有地址1.基本介绍TCP/IP协议的心脏是网络层,主要“实现节点之间的通信”,即“点对点(end-to-end)通信”。网络层包含IP(InternetProtocol)及DNS(DomainNameSystem)、ARP(AddressResolutionPro......
  • 一文说透Linux编译特定内核版本的方法(ubuntu和树莓派)
    更多内容在在做开发的时候,我们可能会针对某个内核版本进行驱动的编写。这个时候就需要把版本编译到这个特定的内核版本。本文介绍ubuntu和树莓派两种环境系统的内核编译方式Ubuntu:已编译到5.9.0内核为例1将内核安装包和内核配置config放到虚拟机或PC机下2更新apt源,并安......