HashMap 的 put 方法源码分析(JDK 1.8)

news/2025/2/13 22:44:23/文章来源:https://www.cnblogs.com/jock766/p/18714537

一、HashMap 的 put 方法源码分析(JDK 1.8)

以下是 HashMap 的 put 方法的源码(JDK 1.8):


hash(key) 方法


hash(key) 方法用于计算键的哈希值:

  • 如果键为 null,返回 0。

  • 否则,返回键的哈希码与高 16 位的异或结果(目的是减少哈希冲突)。


putVal 方法


putVal 方法是 HashMap 的核心方法,用于将键值对插入哈希表中。以下是 putVal 方法的源码:

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {Node<K,V>[] tab; // 哈希表数组Node<K,V> p;     // 当前节点int n, i;        // n: 哈希表长度; i: 索引位置// 如果哈希表为空或长度为 0,进行扩容if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;// 计算索引位置,如果该位置为空,直接插入新节点if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; // 目标节点K k;         // 当前节点的键// 检查第一个节点是否匹配if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;// 如果节点是树节点,调用红黑树的插入方法else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {// 遍历链表for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {// 插入新节点p.next = newNode(hash, key, value, null);// 如果链表长度超过阈值,转换为红黑树if (binCount >= TREEIFY_THRESHOLD - 1)treeifyBin(tab, hash);break;}// 检查节点是否匹配if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}// 如果找到匹配的节点,更新值并返回旧值if (e != null) {V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}// 修改计数器加 1++modCount;// 如果元素数量超过阈值,进行扩容if (++size > threshold)resize();afterNodeInsertion(evict);return null;}


二、putVal 源码解析


1、初始化哈希表:

  • 如果哈希表为空或长度为 0,调用 resize() 方法进行扩容


2、计算索引位置:

  • 使用 (n - 1) & hash 计算键的存储位置

  • 如果该位置为空,直接插入新节点


3、处理哈希冲突:

  • 如果该位置不为空,检查第一个节点是否匹配

  • 如果节点是树节点,调用红黑树的插入方法

  • 否则,遍历链表,插入新节点或更新值


4、更新值:

  • 如果找到匹配的节点,更新值并返回旧值


5、扩容:

  • 如果元素数量超过阈值,调用 resize() 方法进行扩容


三、JDK 1.8 中 HashMap 的 resize() 方法源码分析


resize() 是 HashMap 中的一个核心方法,用于在哈希表容量不足时进行扩容。扩容的目的是为了减少哈希冲突,提高 HashMap 的性能。在 JDK 1.8 中,resize() 方法不仅负责扩容,还负责在扩容时重新分配键值对的位置


1、resize() 方法的源码

以下是 HashMap 的 resize() 方法的源码(JDK 1.8):

    final Node<K,V>[] resize() {Node<K,V>[] oldTab = table; // 旧的哈希表int oldCap = (oldTab == null) ? 0 : oldTab.length; // 旧容量int oldThr = threshold; // 旧阈值int newCap, newThr = 0; // 新容量和新阈值// 计算新容量和新阈值if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) { // 如果旧容量已达到最大值threshold = Integer.MAX_VALUE; // 阈值设置为最大值return oldTab; // 直接返回旧表,不再扩容}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && // 新容量为旧容量的 2 倍oldCap >= DEFAULT_INITIAL_CAPACITY) // 如果旧容量大于默认初始容量newThr = oldThr << 1; // 新阈值为旧阈值的 2 倍}else if (oldThr > 0) // 如果旧阈值大于 0(初始化时指定了容量)newCap = oldThr; // 新容量为旧阈值else { // 如果旧容量和旧阈值都为 0(默认初始化)newCap = DEFAULT_INITIAL_CAPACITY; // 新容量为默认初始容量(16)newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 新阈值为默认负载因子 × 默认初始容量}// 如果新阈值为 0,重新计算if (newThr == 0) {float ft = (float)newCap * loadFactor; // 新容量 × 负载因子newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE); // 如果未超过最大容量,设置为 ft,否则设置为最大值}threshold = newThr; // 更新阈值@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // 创建新表table = newTab; // 更新哈希表// 如果旧表不为空,重新分配键值对if (oldTab != null) {for (int j = 0; j < oldCap; ++j) { // 遍历旧表Node<K,V> e;if ((e = oldTab[j]) != null) { // 如果当前桶不为空oldTab[j] = null; // 清空旧桶if (e.next == null) // 如果当前桶只有一个节点newTab[e.hash & (newCap - 1)] = e; // 直接放入新表else if (e instanceof TreeNode) // 如果当前桶是红黑树((TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 拆分红黑树else { // 如果当前桶是链表Node<K,V> loHead = null, loTail = null; // 低位链表头尾节点Node<K,V> hiHead = null, hiTail = null; // 高位链表头尾节点do {if ((e.hash & oldCap) == 0) { // 如果哈希值的对应位为 0if (loTail == null) // 如果低位链表为空loHead = e; // 设置头节点elseloTail.next = e; // 添加到尾部loTail = e; // 更新尾节点}else { // 如果哈希值的对应位为 1if (hiTail == null) // 如果高位链表为空hiHead = e; // 设置头节点elsehiTail.next = e; // 添加到尾部hiTail = e; // 更新尾节点}} while ((e = e.next) != null); // 遍历链表// 将低位链表放入新表的原位置if (loTail != null)loTail.next = null;newTab[j] = loHead;// 将高位链表放入新表的新位置(原位置 + 旧容量)if (hiTail != null)hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}return newTab; // 返回新表}


四、resize() 方法源码解析


1、计算新容量和新阈值

  • 如果旧容量大于 0:

    • 如果旧容量已达到最大值(MAXIMUM_CAPACITY),直接返回旧表,不再扩容

    • 否则,新容量为旧容量的 2 倍,新阈值为旧阈值的 2 倍

  • 如果旧容量为 0 但旧阈值大于 0(初始化时指定了容量),新容量为旧阈值

  • 如果旧容量和旧阈值都为 0(默认初始化),新容量为默认初始容量(16),新阈值为默认负载因子 × 默认初始容量


2、创建新表

  • 根据新容量创建新表(newTab)

  • 更新哈希表引用(table = newTab)


3、重新分配键值对

  • 遍历旧表的每个桶:

    • 如果当前桶只有一个节点,直接放入新表

    • 如果当前桶是红黑树,调用 split() 方法拆分红黑树

    • 如果当前桶是链表,将链表拆分为低位链表和高位链表:

      • 低位链表:哈希值的对应位为 0,放入新表的原位置

      • 高位链表:哈希值的对应位为 1,放入新表的新位置(原位置 + 旧容量)


4、返回新表

  • 返回扩容后的新表


五、resize() 方法的关键点


1、扩容时机:

  • 当 HashMap 中的元素数量超过阈值(容量 × 负载因子)时,触发扩容


2、扩容机制:

  • 新容量为旧容量的 2 倍

  • 新阈值为旧阈值的 2 倍


3、键值对重新分配:

  • 通过 (e.hash & oldCap) 判断键值对应该放入低位链表还是高位链表。

  • 低位链表放入新表的原位置,高位链表放入新表的新位置(原位置 + 旧容量)


4、红黑树拆分:

  • 如果桶是红黑树,调用 split() 方法将红黑树拆分为两个链表或红黑树


六、resize() 方法的示例

以下是一个 HashMap 扩容的示例:


七、总结

resize() 是 HashMap 的核心方法之一,负责在容量不足时进行扩容。它的主要逻辑包括:

  • 1、计算新容量和新阈值。

  • 2、创建新表。

  • 3、重新分配键值对。

  • 4、返回扩容后的新表。

通过扩容,HashMap 可以减少哈希冲突,提高性能。理解 resize() 方法的实现原理,有助于更好地使用和优化 HashMap

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

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

相关文章

第二章笔记

2.1用二进制数表示计算机信息的原因 IC的所有引脚,只有直流电压0V或5V 两个状态。也就是说,IC的一个引脚,只能表示两个状态。 计算机处理信息的最小单位——位,就相当于二进制中的一位。位的英文bit是二进制数位(binary digit)的缩写。 二进制数的位数一般是8位、16位、3…

基金年结、结账、关账和开账

基金年结、结账、关账和开账是基金公司年底财务工作中的重要环节,不仅涉及资产、负债、所有者权益等七大要素的核算,还需要进行全面的账务处理和数据校验。本文将详细解读基金年结的背景、目的、概念以及具体流程,帮助大家深入了解这一复杂而关键的财务操作过程。其实年结不…

分组密码工作模式-CBC

CBC全称密文分组链接工作模式:是分组密码算法的一种工作模式,其特征是将当前的明文分组与前一密文分组进行异或运算后再进行加密得到当前的密文分组。 在CBC模式下,每个明文分组在加密之前,先与反馈至输入端的前一组密文分组按位异或后,再送至加密模块进行加密。其中,IV是…

基于AutoEncode自编码器的端到端无线通信系统matlab误码率仿真

1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印):仿真操作步骤可参考程序配套的操作视频。2.算法涉及理论知识概要自编码器是一种特殊的神经网络结构,主要由编码器(Encoder)和解码器(Decoder)两部分组成。自编码器的目标是最小化重构误差,常用的重构误…

2-EasyARM i.MX287A开发板 imx-uboot 主线移植

https://github.com/nxp-imx/linux-imx 这里我们使用 lf-6.1.55-2.2.2 分支开始 首先通过硬件原理图找出 ZLG imx287 和 官方开发板 mx28evk 的差异调试串口 mx28evkZLG287这里可以看到官方板用的GPIO3_16/17作为调试串口,ZLG287 GPIO3_16/17被用作I2C,没有接口引出,DUART使…

Langchain的底层原理

Langchain的应用场景 1.个人助手:预定航班 2.学习辅助:参考整个大纲 3.数据分析和数据科学:连接到公司和客户的数据,极大的促进数据的分析

国内服务器docker设置代理【2024年12月】在debian12上测试通过

搬运up主凌冰Koori使用export https_proxy这类环境变量已经失效了。现在可以编辑下面的文件:/etc/docker/daemon.json可能对你来说会提示是新文件,不用管。加入以下配置:{“proxies”: {“http-proxy”: “socks5://127.0.0.1:2080”,“https-proxy”: “socks5://127.0.0.1…

Communication Efficient Large-Scale Training with Adams Convergence Speed

目录概1-bit Adam1-bit SGD代码Seide F., Fu H., Droppo J., Li G. and Yu D. 1-bit stochastic gradient descent and its application to data-parallel distributed training of speed dnns. 2014.Tang H., Gan S., Awan A. A., Rajbhandari S., Li C., Lian X., Liu J., Zh…

基于粒子群算法的网络最优节点部署优化matlab仿真

1.程序功能描述基于粒子群算法的网络最优节点部署优化,实现WSN网络的节点覆盖最大化。 2.测试软件版本以及运行结果展示MATLAB2022A版本运行 3.核心程序%使用PSO优化剩余WSN节点位置以覆盖洞 Numv = 2*(N); func = @(x)fobjs(x,Rmax,area); Vmin = zeros(Numv,1);…

边坡智能监测识别摄像头

边坡智能监测识别摄像头具备24小时不间断的视频监控能力,可以随时捕捉到边坡的动态变化,并记录所有视频数据。通过深度学习模型,该设备可以自动识别不同类型的异常现象,包括土体位移、裂缝扩展等,大幅提升检测准确率。一旦发现异常情况,系统会立即向相关人员发送警报信息…

AI滴漏监测识别摄像机

AI滴漏监测识别摄像机具备24小时不间断的视频监控能力,可以随时查看现场情况,并记录所有视频数据。一旦检测到液体泄漏,系统会立即向管理人员发送警报信息,以便迅速采取措施进行处理。AI滴漏监测识别摄像机设计坚固,可以在各种复杂环境中稳定工作,包括极端温度、高湿度等…

Fastjson反序列化漏洞原理与漏洞复现

根据这位大佬文章学习 https://blog.csdn.net/Bossfrank/article/details/130100893 Fastjson反序列化 一:json是啥 json是一种格式json全称是JavaScript object notation。即JavaScript对象标记法,使用键值对进行信息的存储。点击查看代码 {"name":"BossFran…