【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…

LeetCode119. Pascal‘s Triangle II

文章目录 一、题目二、题解 一、题目 Given an integer rowIndex, return the rowIndexth (0-indexed) row of the Pascal’s triangle. In Pascal’s triangle, each number is the sum of the two numbers directly above it as shown: Example 1: Input: rowIndex 3 Ou…

10个linux文件管理命令

1. ls – 列出目录内容 ls可能是每个Linux用户在其终端中键入的第一个命令。它允许您列出您想要的目录的内容&#xff08;默认情况下是当前目录&#xff09;&#xff0c;包括文件和其他嵌套目录。 它有很多选择&#xff0c;所以最好使用 --help 来获得一些帮助。此标志返回所…

卡码网Java基础课 | 6. 数组的倒序与隔位输出

卡码网Java基础课|6. 数组的倒序与隔位输出 数组ArrayList增强for循环6. 数组的倒序与隔位输出 数组 Java中的数组是一种用于存储相同数据类型的元素的数据结构。 相同数据类型的元素指的是数组中的所有元素都必须是相同的数据类型&#xff1b;固定大小&#xff0c;连续存储&…

yum安装及常用操作

yum安装及常用操作 1. 安装yum2. yum常用命令2.1. 基本语法2.2. [option] 常用选项2.3. [command] 常用命令 1. 安装yum 使用wget下载yum的安装包&#xff0c;这里以CentOS 7为例&#xff0c;如果是其他版本的系统&#xff0c;请根据实际情况修改下载链接&#xff0c;如果wget也…

华为三层交换机通 过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…

DNS重绑定攻击记录(绕过同源策略、绕过IP黑名单、SSRF绕过)

目录 概念 DNS重绑定情景举例认识 DNS绑定机制 DNS重要记录类型 域名解析过程 TTL 请求域名解析

如何进行sql优化?

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

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

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

03 详细的Git命令使用大全

常用命令&#xff1a; git init&#xff1a;初始化一个新的Git仓库。git add <文件名>&#xff1a;将文件添加到暂存区&#xff0c;准备进行提交。git commit -m "备注"&#xff1a;提交暂存区的文件到仓库&#xff0c;并添加提交备注。git status&#xff1a;…

算法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. 根据以…

使用高版本JDK编译低版本代码

背景 SonarQube运行于Java17&#xff0c;使用Sonar的Maven插件编译时&#xff0c;如果编译使用的JDK版本低于SonarQube使用的Java17&#xff0c;则会提示Java文件不匹配问题。 Error during SonarScanner execution java.lang.UnsupportedClassVersionError: org/sonar/batch/…

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 …

浏览器刷新页面,缓存的处理方式,强制刷新

刷新页面的缓存处理的方式对比 地址栏回车/直接访问 URL保留强缓存&#xff0c;保留协商缓存&#xff0c;走正常请求流程点击浏览器刷新按钮忽略强缓存&#xff0c;保留协商缓存按f5【command r】忽略强缓存&#xff0c;保留协商缓存ctrl f5 【command shift r 】忽略强缓…

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;此时需要统计每个业务…