【Java集合篇】HashMap 是如何扩容的

在这里插入图片描述

HashMap 是如何扩容的

  • ✔️ 为什么需要扩容?
  • ✔️ 桶元素重新映射
  • ✔️链表重新链接
  • ✔️ 取消树化
  • ✔️拓展知识仓
    • ✔️除了rehash之外,哪些操作也会将树会退化成链表?


✔️ 为什么需要扩容?


HashMap在Java等编程语言中被广泛使用,用于存储键值对数据。HashMap的实现原理是基于哈希表,通过哈希函数将键转化为桶的位置,从而实现快速查找、插入和删除操作。


然而,当HashMap中的元素数量增加时,哈希冲突的概率也会随之增加,这可能导致HashMap的性能下降。具体来说,当一个桶中的元素过多时,查询、插入和删除操作的时间复杂度会变为O(n),这违背了HashMap设计的初衷。为了解决这个问题,HashMap会在必要时进行扩容。


扩容的目的是为了增加HashMap的容量,从而减少哈希冲突的概率,提高查询、插入和删除操作的性能。扩容的过程涉及到重新计算所有元素的哈希值和重新分配元素到新的桶中,这是一个相对耗时的操作。因此,为了平衡性能和扩容的开销,通常会将HashMap的容量设定为一个较大的初始值,并在需要时进行扩容。


另外,扩容还可以帮助维持HashMap的性能稳定性。由于负载因子是存储元素的数量与哈希表容量的比值,当负载因子过高时,哈希表的性能会下降。通过适时地扩容,可以控制负载因子在一个合适的范围内,从而保证HashMap的性能。


HashMap需要扩容是为了减少哈希冲突的概率,提高查询、插入和删除操作的性能,以及维持性能稳定性。


当我们在Java 中使用 HashMap 时,可以通过以下代码示例来理解扩容的原理:

import java.util.HashMap;public class HashMapExample {public static void main(String[] args) {// 创建一个初始容量为16,负载因子为0.75的HashMapHashMap<String, Integer> map = new HashMap<>(16, 0.75f);// 添加元素到HashMap中map.put("Alice", 25);map.put("Bob", 30);map.put("Charlie", 35);map.put("David", 40);// 输出当前HashMap的容量和已使用容量System.out.println("Initial capacity: " + map.capacity());System.out.println("Used buckets: " + map.size());// 添加更多元素,触发扩容map.put("Eve", 45);map.put("Frank", 50);map.put("Grace", 55);map.put("Henry", 60);// 再次输出当前HashMap的容量和已使用容量System.out.println("Current capacity: " + map.capacity());System.out.println("Used buckets: " + map.size());}
}

在上述代码中,我们创建了一个初始容量为16,负载因子为0.75的HashMap。当我们向HashMap中添加元素时,如果已使用容量超过了负载因子与当前容量的乘积,HashMap就会触发扩容。扩容后,HashMap的容量会增加,从而减少哈希冲突的概率,提高查询、插入和删除操作的性能。


参考前两篇博文:

【Java集合篇】负载因子和容量的关系
【Java集合篇】为什么HashMap的Cap是2^n,如何保证?


假设现在散列表中的元素已经很多了,但是现在散列表的链化已经比较严重了,哪怕是树化了,时间复杂度也没有O(1)好,所以需要扩容来降低Hash冲突的概率,以此来提高性能。


我们知道,当 ++size >threshold 之后详见java.util.HashMap#putVal 方法),HashMap就会初始化新的新的桶数组,该桶数组的size为原来的两倍,在扩大桶数组的过程中,会涉及三个部分:


1 . 如果某桶节点没有形成链表,则直接rehash到其他桶中


2 . 如果桶中形成链表,则将链表重新链接


3 . 如果桶中的链表已经形成红黑树,但是链表中的元素个数小于6,则进行取消树化的操作


✔️ 桶元素重新映射


如果桶中只有一个元素,没有形成链表,则将原来的桶引用置为null,同时,将该元素进行 rehash 即可,如下代码所示:


if (e.next == null) {newTab[e.hash & (newCap - 1)] = e;
}

在Java的HashMap中,当元素数量增加到一定阈值时,为了提高性能和减少哈希冲突,会触发桶的重新映射(rehashing)。这是通过扩容来实现的。

桶的重新映射过程涉及到以下步骤:

  1. 计算新的容量:扩容时,新的容量通常是旧容量的一个固定倍数(例如,默认情况下是原容量的两倍)。
  2. 生成新的桶数组:根据新的容量,创建一个新的桶数组。这个数组的长度是原来的两倍(在默认情况下)。
  3. 重新计算哈希值:遍历旧桶中的每个元素,并使用新的哈希函数重新计算它们的哈希值。这是为了确保元素能够均匀分布在新的桶数组中,减少哈希冲突。
  4. 重新放置元素:根据新计算的哈希值,将每个元素放置在新的桶数组中的适当位置。
  5. 更新HashMap状态:更新HashMap的容量、大小以及相关的内部状态。

这个过程是必要的,因为随着元素的增加,哈希冲突的概率也会增加,导致性能下降。通过扩容和重新映射,可以维持HashMap的高效性能。


✔️链表重新链接


假设有4个key,分别为a,b,c,d,且假定他们的hash值如下:


hash(a) = 3;  hash(a) & 7 = 3;    hash(a) & 8 = 0;
hash(b) = 11;  hash(b) & 7 = 3;   hash(b) & 8 = 8;
hash(c) = 27;  hash(c) & 7 = 3;   hash(c) & 8 = 8;
hash(d) = 59;  hash(d) & 7 = 3;hash(d) & 8 = 8;

假如此时HashMap的cap为 8,某个桶中已经形成链表,则可得到: table[3]=a->b->c->d。


如果此时扩容,将newCap设为16,我们可以看到如下结果:


hash(a) = 3; hash(a) & 15 = 3;
hash(b) = 11; hash(b) & 15 = 11;
hash(c) = 27; hash(c) & 15 = 11;
hash(d) = 59; hash(d) & 15 = 11;

我们会发现,当hash(k) & oldCap = 0 (即hash(a) = 3;的这个记录)时,这些链表的节点还是在原来的节点中(扩容后他的结果还是3) ,同时如果hash(k) & oldCap != 0时(11 27 59这几条记录),这些链表的节点会到桶中的其他的位置中 (从3变成了11)。


所以,对于链表来说,我们就不用逐人节点重新映射,而是直接通过hash(k) & ldCap进行分类,之后统一移动他们的位置即可。源码如下:


Node<K,V> loHead = null,loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null) {loHead = e;} else {loTail.next = e;}loTail = e;} else {if (hiTail == null) {hiHead = e;} else {hiTail.next = e;}hiTail = e;}
} while((e = next) != null);
if (loTail != null) {loTail.next = null;newTab[j] = loHead;
}if (hiTail != null)  {hiTail.next = null;newTab[j + oldCap] = hiHead;
}

✔️ 取消树化


有了上面链表重新连接的经验,我们会发现,其实树化后的节点,也可以使用该操作来降低红黑树每人节点rehash时的时间复杂度,所以红黑树的 TreeNode 继承了链表的Node类,有了next字段,这样就可以像链表一样重新链接,源码如下:


TreeNode<K,V> loHead = null,loTail= null;
TreeNode<K,V> hiHead = null, hiTail = null;
for (TreeNode<K,V> e = b, next; e != null; e = next)  {next = (TreeNode<K,V>)e.next;e.next = null;if ((e.hash & bit) == 0) {if ((e.prev = loTail) == null)loHead = e;else loTail.next = e;loTail = e;++lc;} else {if ((e.prev = hiTail) == null)hiHead = e;elsehiTail.next = e;hiTail = e;++hc;}
}

当上面的操作完结后,HashMap会检测两个链表的长度,当元素小于等于6的时候,就会执行取消树化的操作,否则就会将新生成的链表重新树化。


取消树化非常简单,因为之前已经是条链表了,所以只需要将里面的元素由TreeNode转为Node即可。


至于重新树化的过程,请听下回分解~


✔️拓展知识仓


✔️除了rehash之外,哪些操作也会将树会退化成链表?


remove 元素的时候,这个过程中也会做退化的判断,如以下代码中,也会在这个分支中执行退化的操作(untreeify),如下代码所示:


if (root == null (movable 8& (root.right == null  (rl = root.left) == null rl.left == null))) {tab[index] = first.untreeify(map); // too smallreturn;
}

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

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

相关文章

【QML COOK】- 001-添加资源文件

1. 下图为要添加的资源文件 2. 将资源文件放置在工程目录中 我放在【Resources/Images】下&#xff0c;你随意 3. 添加qrc类型文件 文件->New File... 选择 Qt->Qt Resource File 填好文件名。我填“Images”你随意 出现名为“Images.qrc”的qrc类型文件 4. 添加资源文…

Ansible:简单、快速、安全、最强大的 IT 自动化系统 | 开源日报 No.140

ansible/ansible Stars: 59.6k License: GPL-3.0 Ansible 是一个极其简单的 IT 自动化系统&#xff0c;它处理配置管理、应用部署、云提供、临时任务执行、网络自动化和多节点编排。Ansible 使得像零停机滚动更新与负载均衡器一样复杂的更改变得容易。主要功能包括&#xff1…

【MATLAB第89期】基于MATLAB的差分自回归滑动平均模型ARIMA时间序列预测模型含预测未来

【MATLAB第89期】基于MATLAB的差分自回归滑动平均模型ARIMA时间序列预测模型含预测未来 往期文章 【MATLAB第82期】基于MATLAB的季节性差分自回归滑动平均模型SARIMA时间序列预测模型含预测未来 一、模型介绍 1、模型简介 差分自回归移动平均模型&#xff08;Autoregressiv…

【BIAI】Lecture 5 - Auditory system

Lecture 5 - Auditory system 专业术语 auditory system 听觉系统 pinna 耳廓 auditory canal 耳道 tympanic membrane 鼓膜 cochlea 耳蜗 ossicles 听骨 auditory-vestibular nerve 前庭神经 oval window 椭圆窗 attenuation reflex 衰减反射 tensor tympani muscle 鼓膜张肌…

网络安全与IP地址:构建数字世界的前沿堡垒

网络安全是当今数字社会中不可忽视的挑战之一。而IP地址&#xff0c;作为互联网通信的基础协议&#xff0c;既是数字化时代的桥梁&#xff0c;也是网络安全的关键节点。本文将剖析IP地址在网络安全领域的作用&#xff0c;以及如何利用其特性建立有效的网络安全策略。 IP地址&a…

华为三层交换机通 过VLANIF虚拟接口实现跨VLAN通信

S1配置 vlan batch 2 to 3interface Vlanif2ip address 192.168.2.254 255.255.255.0interface Vlanif3ip address 192.168.3.254 255.255.255.0interface GigabitEthernet0/0/2port link-type accessport default vlan 2interface GigabitEthernet0/0/3port link-type access…

如何进行sql优化?

在日常工作中都避免不了要和各种SQL语句打交道&#xff0c;无论是开发还是后期维护&#xff0c;一条执行效率高的SQL语句都会对系统性能产生巨大影响。那么&#xff0c;如何进行有效的SQL优化呢&#xff1f;下面将为大家深入浅出地讲解SQL优化的各个方面&#xff1a; 1、了解数…

社科院与美国杜兰大学金融管理硕士项目——勇当开路先锋,争做事业闯将

随着金融行业的不断发展&#xff0c;在职金融人员面临着越来越多的机遇和挑战。在这个充满变革的时代&#xff0c;金融人员需要具备开拓进取的精神&#xff0c;勇当开路先锋&#xff0c;争做事业闯将。只有这样&#xff0c;才能在激烈的竞争中立于不败之地&#xff0c;为企业创…

算法32:针对算法31货币问题进行扩展,并对从左往右模型进行总结

本算法是在算法31的基础之上进行推理总结的&#xff0c;因此&#xff0c;在看本章之前&#xff0c;必须先去了解算法31&#xff0c;否则会觉得莫名其妙。 算法31的推理过程&#xff1a; 如果 x y1 y2 y3 y4 y5 y6. x1 y2 y3 y4 y5 y6 那么 x y1 x1. 根据以…

Codeforces Round 911 C. Anji‘s Binary Tree

原题&#xff1a; C. Anji’s Binary Tree time limit per test 2.5 seconds memory limit per test 256 megabytes input standard input output standard output Keksic keeps getting left on seen by Anji. Through a mutual friend, he’s figured out that Anji really …

ECharts 图表简单示例,中国地图

目录 ECharts官网链接: [ECharts](https://echarts.apache.org/zh/index.html)在项目中引入 Apache ECharts柱状图折线图饼图仪表盘中国地图完整示例代码 ECharts官网链接: ECharts 在项目中引入 Apache ECharts <!DOCTYPE html> <html><head><meta char…

JavaWeb——后端AOP面向特定方法编程

七、AOP 1. 概述 AOP&#xff08;Aspect Oriented Programming&#xff09;&#xff1a;面向切面编程、面向方法编程&#xff0c;其实就是面向特定方法编程 场景&#xff1a; 案例部分功能运行较慢&#xff0c;定位执行耗时较长的业务方法&#xff0c;此时需要统计每个业务…

2024年数学建模美赛能用chatGPT之类的AI吗?官方给了明确规定!

这两年chatGPT等大语言模型火了&#xff0c;能对话&#xff0c;自然也能回答数学建模方面的问题。 那美赛能不能用这些AI呢&#xff1f;2024年美赛官方对chatGPT等的使用做出了明确的规定&#xff08;其中的VI. Contest Instructions部分&#xff09;&#xff1a; https://ww…

JavaScript高级程序设计读书记录(六):定型数组,Map

1. 定型数组 定型数组&#xff08;typed array&#xff09;是 ECMAScript 新增的结构&#xff0c;目的是提升向原生库传输数据的效率。实际上&#xff0c;JavaScript 并没有“TypedArray”类型&#xff0c;它所指的其实是一种特殊的包含数值类型的数组。 1.1 历史 随着浏览器…

LaTex引用字体变色

使用下面这条语句进行修改。 ‘citecolor’改变参考文献颜色&#xff0c; ‘linkcolor’改变图标公式引用的颜色&#xff0c; ‘urlcolor’ 文本网站超链接颜色。 \usepackage[colorlinks,bookmarksopen,bookmarksnumbered,citecolorblue, linkcolorblue, urlcolorblue]{hyper…

杨中科 ASP.NET Core前后端分离开发

一、 前后端分离 1、传统MVC开发模式: 前后端的代码被放到同一个项目中&#xff0c;前端人员负责编写页面的模板&#xff0c;而后端开发人员负责编写控制器和模型的代码并且“套模板”。 缺点: 互相依赖&#xff0c;耦合性强&#xff0c;责任划分不清。 2、主流的“前后端分离…

【openGauss服务器端工具的使用】

【openGauss服务器端工具的使用】 gs_checkperf openGauss 不仅提供了gs_checkperf工具来帮助用户了解openGauss的负载情况。 使用数据库安装用户登录服务器&#xff0c;执行如下命令进行查看数据库性能&#xff1a; 简要信息展示&#xff1a;[ommopengauss03 ~]$ gs_checkperf…

跨平台的传输协议@WebDav协议@windows系统配置WedDav服务器@局域网内的WebDav传输系统

文章目录 WebDav协议基本信息启用必要的windows功能启动站点管理器IIS站点根目录访问权限设置站点的功能设置端口通行防火墙IMME文件类型(文件后缀)其他设备登录和访问本机的WebDav服务站点 小结优点缺点 refs WebDav 协议基本信息 来自wikipedia:基于Web的分布式编写和版本控…

协程池与新脚本语言

今天的主人公名为——Melang。 这是一款使用C语言开发的“新”的脚本语言&#xff0c;然而其已经默默问世了6年之久。 下面笔者就带你走进Melang world。 What is Melang Melang是一款协程并发脚本语言。它是一款解释型&#xff0c;而非编译型语言。 在Melang中&#xff…

Tmux 使用小记

本文参考自 阮一峰老师Tmux 使用教程[1] Tmux,不仅仅是分屏那么简单。。。 与tmux类似的工具是screen 会话管理 将窗口与会话"解绑" 对于没有图形界面只有shell的场景(如服务器)&#xff0c;尤其有用..这是其最核心解决的问题(窗口管理啥的只能算锦上添花的辅助功能)…