HashMap的put源码解析

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

思维导图文档地址:

【腾讯文档】HashMap扩容思维导图

https://docs.qq.com/mind/DWE5nWm5YZ1lFZUdD

【腾讯文档】hashmap的put过程思维导图

https://docs.qq.com/mind/DWEZDTHlvdmNDVmxY

下面是具体源码过程:

  1. put(K key, V value)

    1. hash(Object key):通过key的高16位和低16位的异或操作获取hash值

  2. putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)

    1. node table没有初始化时:resize(),以默认容量16初始化数组

    2. (p = tab[i = (n - 1) & hash]) == null 当没有hash冲突时直接把node节点放入tab中

    3. 有hash冲突

      1. p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))当是同一个key时覆盖旧key的value

      2. p instanceof TreeNode当是树节点时,把new node放到红黑树中

      3. 否则就是单向链表,for (int binCount = 0; ; ++binCount),遍历单链表

        1. (e = p.next) == null当遍历到链表的最后一个元素时把新元素挂到尾部,binCount >= TREEIFY_THRESHOLD - 1 treeifyBin(tab, hash)同时检查链表长度是否超过8,超过则转成红黑树

        2. if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))每次遍历时都会判断key是否一样,一样则结束遍历,然后覆盖旧value

    4. if (e != null)table中具有同样的key,则覆盖旧value,并返回旧value

    5. ++modCount;这个跟之前讲的集合一样,主要是防止你在遍历集合时对集合put和remove操作

    6. ++size > threshold,判断是否需要扩容threshold = table容量*加载因子

  3. resize() 扩容

    1. oldCap > 0, newCap = oldCap << 1,newThr = oldThr << 1;除了之前的table初始化,正常新的table容量是旧table容量的两倍,扩容阈值也是原来的2倍

    2. threshold = newThr;扩容阈值也是原来阈值的2倍

    3. oldTab != null,for (int j = 0; j < oldCap; ++j),当旧table不为空时进行元素迁移

    4. if ((e = oldTab[j]) != null),j位置有元素时进行遍历j位置上的元素进行元素迁移

      1. e.next == null,j位置元素没有hash冲突则用e.hash & (newCap - 1)定位新tab的位置放入

      2. e instanceof TreeNode,split(this, newTab, j, oldCap)如果是红黑树则遍历树进行元素迁移,for (TreeNode<K,V> e = b, next;e != null; e = next)遍历红黑树

        1. (e.hash & bit) == 0如果e的hash跟旧tab容量与的结果是0说明跟新数组e.hash & (newCap - 1)操作时位置结果还是index位置(e.prev = loTail) == nullloHead = e说明此时链表还是空的,此时e是头节点,否则 loTail.next = e;在尾部放入e,此时loTail = e;e就是新的尾部

        2. 否则(e.hash & bit) != 0如果e的hash跟旧tab容量与的结果不是0说明跟新数组e.hash & (newCap - 1)操作时位置结果不是index位置,而是 bit+index,此时就需要挪动(e.prev = hiTail) == nullhiHead= e说明此时链表还是空的,此时e是头节点,否则hiTail.next = e;在尾部放入e,此时hiTail= e;e就是新的尾部

        3. loHead != null不需要挪动位置的元素存在时

          1. lc <= UNTREEIFY_THRESHOLD,loHead.untreeify(map)如果少于六个元素,则变为链表并放到新数组的index位置。loHead.treeify(tab);否则维护红黑树,并放到新数组的index位置

          2. lc <= UNTREEIFY_THRESHOLD,loHead.untreeify(map)如果少于六个元素,则变为链表并放到新数组的index + bit位置。loHead.treeify(tab);否则维护红黑树,并放到新数组的index + bit位置

      3. 否则就是单向链表,do{}while()遍历单向链表

        1. (e.hash & oldCap) == 0,如果e的hash跟旧tab容量与的结果是0说明跟新数组,e.hash & (newCap - 1)操作时位置结果还是j位置,loTail == null,loHead = e,说明不需要挪动的链表还是空的,此时e是头节点,否则loTail.next = e;在尾部放入e,此时loTail = e;e就是新的尾部

        2. 否则(e.hash & oldCap) != 0,如果e的hash跟旧tab容量与的结果不是0说明跟新数组,e.hash & (newCap - 1)操作时位置结果不是j位置,而是 j+oldCap,此时就需要挪动hiTail == null,hiHead= e,说明此时需要挪动还是空的,此时e是头节点,否则hiTail.next = e;在尾部放入e,此时hiTail= e;e就是新的尾部

        3. loTail!= null不需要挪动位置的元素存在时

          1. loTail.next = null;newTab[j] = loHead;把链表放入新数组的j位置

        4. hiTail != null需要挪动位置的元素存在时

          1.  hiTail.next = null;newTab[j + oldCap] = hiHead;把链表放到新数组的j + oldCap位置

对整个过程中的一些总结:

  1. 整个put过程是按没有hash冲突,有hash冲突两个大流程来分的,当有hash冲突时通过instanceof来判断是否是TreeNode的对象(这个是我们可以借鉴的地方,在我们实际开发中可能也会判断某个对象是否是某个类的对象)。

  2. 在进行tab扩容时不管是TreeNode还是单向链表,都是先生成两个单向链表:一个不需要挪动位置的链表,一个是需要挪动位置的链表,是TreeNode时再通过判断链表的长度来判断是否需要维护红黑树。而且这个地方巧妙的利用的tab扩容是旧tab的2倍,而且tab的容量是2的倍数这个特性,在判断是否需要挪动元素使用的是(e.hash & oldCap) == 0这个方式,而没有使用e.hash & (newCap - 1)这种方式,这个扩容特性确定了不需要挪动的元素就在j位置,需要挪动的元素是j+oldCap,这样也就不需要像1.8之前使用头插法了。

  3. 巧妙的程序设计总是能事半功倍的,在日常开发中多想想程序设计的合理性,这可能就是拉开你和别人差距的一种方式

  4. 公众号同名,欢迎关注

HashMap 源码解析及 get、put、resize 方法详解 HashMap 源码解析及 get、put、resize 方法详解HashMapJava 中常用的集合类之一,它基于哈希表实现,用于存储键值对。在本篇文章中,我们将深入分析 HashMap源码,并详细解释其中的 get、put 和 resize 方法。HashMap 类的源码非常庞大,我们将着重关注其中的 get、put 和 resize 方法。首先,我们来看一下 HashMap 的基本结构。HashMap 是一个泛型类,它继承了 AbstractMap 类并实现了 Map 接口。 阅读详情

相关推荐

【C++】 详解 lower_bound 和 upper_bound 函数(看不懂来捶我!!!)

这两个函数是我在 LeetCode 上做题见到,看到不熟悉的函数 lower_bound 和 upper_bound让我感觉很难受,于是在C++ 官网去学习,例子就一个是最基础的,我看明白了。虽然是两个函数的接口就两个,但是有时候看别人使用的时候,里面参数还可以放不同的仿函数,我懵逼了。就去网上搜,但是大家讲解的都是它的第一个接口。我只能再把文档一遍一遍过,代码一遍遍的尝试,调试。最终通过查阅资料将其总结如下。

weixin_45031801的博客 9万+

解读HashMapput方法的源码

由于重写了hashCode方法,致使strKey的hashCode和 strKey2的hashCode相同,但是他们equals的结果却不相同,这里使strKey和strKey2的hashCode相同的目的是为了制造散列冲突(散列冲突指的是 ”桶“ 已经被填充的现象,也就是元素对应的node数组下标的位置已经有值了),即由于hashCode相同导致不同的元素存放在node数组的同一位置,要注意hashCode即便是相同的也会出现散列冲突,这里只是为了模拟散列冲突所以设置了相同的hashCode。

qq_45593903的博客 1256

深入STM32 USB OTG FS:从官方MSC例程到实战双存储设备移植全记录

本文详细解析了STM32 USB OTG FS在双存储设备开发中的实战应用,从协议栈底层机制到SD卡/SPI Flash同步挂载的实现。通过优化SDIO接口和SPI Flash驱动,结合USB MSC类的关键实现,提供了完整的解决方案和性能优化策略,适合嵌入式开发者参考。

weixin_28366475的博客 350

HashMapput方法的源码分析(Java)

JavaHashMap的一点点源码的分析

m0_67683346的博客 1108

Hashmap源码解析以及put流程

.Hashmap的定义 Hashmap是一种常用的数据结构,底层是基于数组和链表实现的。 二.Hashmap的详情结构 2.1 数据结构 HashMap提供了三个构造函数: HashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMapHashMap(int initialCapacity):构造一个带指定初始容...

健康平安的活着的专栏 4905

浅析HashMapput()方法执行流程【转载】

5、如果不是treenode,开始遍历链表,判断链表长度是否大于8,如果大于8就转成红黑树,在树中执行插入操作,如果不是大于8,就在链表中执行插入;HashMap的数据结构在jdk1.8之前是数组+链表,为了解决数据量过大、链表过长是查询效率会降低的问题变成了数组+链表+红黑树的结构,利用的是红黑树自平衡的特点。3、判断table[i]==null,如果是true,直接新建节点进行添加,如果是false,判断table[i]的首个元素是否和key一样,一样就直接覆盖。

泯灭于众生,高歌于孤夜! 678

HashMap中的put方法的源码解析

1.首先调用hash(key)计算hash值 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } 2.判断 传入的key是否等于null(HashMap中是 允许存入null的) 对key值调用原生hashCode方法后进行 右移和异或运算得到 存放的数组的下标 static final int hash(Object key) { int h;

qq_42303467的博客 364

HashMapput方法源码解析

通过上述流程,HashMap实现了高效的键值对存储与检索,设计上兼顾了时间复杂度与空间利用率。:数组容量翻倍(如16→32),并重新计算阈值(新容量 = 旧容量。遍历旧数组,将每个元素重新哈希到新数组(重新计算每个节点的索引)。创建默认容量16的数组,阈值(threshold)为。:更新对应节点的Value,并返回旧值47。将链表转换为红黑树(否则仅扩容数组)48。的Map实现,用于存储键值对。,确定键值对在数组中的存储位置。方法,按红黑树规则插入新节点。:将新节点插入链表末尾。,但位运算效率更高。

qq_40453972的博客 590

带你从源码的角度解析HashMap的get和put流程

HashMapJava集合框架中最重要且最常用的数据结构之一,它基于哈希表实现,提供了高效的键值对存储和检索功能。本文将从源码角度深入分析HashMap的get和put操作流程,使用的JDK版本为1.8。​​get操作​​:计算hash→定位桶位置→检查首节点→遍历链表/树查找​​put操作​​:检查初始化→计算hash→定位桶位置→处理冲突→插入/更新→检查扩容。

weixin_73735141的博客 719

源码角度解析 --- HashMap 的 get 和 put 流程

本文深入分析了Java HashMap的get和put操作实现原理。HashMap采用数组+链表+红黑树结构,JDK8后引入红黑树优化性能。get操作通过hash计算索引,在桶中查找元素,涉及链表遍历或红黑树查询。put操作包含hash计算、冲突处理(链表/红黑树插入)和扩容机制。其中,当链表长度超过8且数组长度≥64时会转换为红黑树。文章从源码角度详细解析了key定位、冲突解决、树化条件等核心机制,揭示了HashMap高效实现键值存储的技术细节。

m0_75163045的博客 926

源码解析HashMapput方法

本篇文章将从源码的角度来很详细地讲解HashMapput方法的执行流程。

深夜无眠的博客 1900

HashMap源码解析PUT流程详解

HashMap源码解析摘要 HashMap是基于哈希表的Map接口实现,采用数组+链表/红黑树结构存储键值对。默认初始容量16,负载因子0.75,当链表长度超过8且数组长度≥64时会转为红黑树。PUT操作核心流程:1)计算key哈希值;2)通过(n-1)&hash确定数组索引;3)处理哈希冲突(链表尾插/红黑树插入);4)必要时扩容。扩容时采用2倍扩容机制,将旧数组元素重新分配到新数组。迭代器采用fail-fast机制保证线程不安全时的快速失败。哈希计算通过key.hashCode()高16位异或运

小电玩 775

解析 HashMap 源码之基本操作 put

之前一直都在使用 HashMap 做一些操作,心里常常默认 HashMap 很快 ,从未做过深究。现在看过源码之后才发现 HashMap 的效率并没有想象中的那么高,O(1) O(n) O(tab数据length * 链表length) 均有可能,并且也不太适合存储大量数据。 put 操作 HashMap<String, String> stringStringHashMap = new HashMap<>(); stringStringHashMap.put("a","a");

shengjk1的博客 264

HashMap的Get(),Put()源码解析

Java 中用于存储键值对(Key-Value)的集合类,它实现了。方法是 HashMap 中用于获取指定键对应值的核心方法.:键(Key)不能重复,若插入重复键会覆盖原有值。首次插入时,数组默认长度为 16。2. 核心数据结构:哈希表(HashTable):不保证元素的存储顺序,也不保证顺序恒定不变。4.插入后检查是否需要扩容。最核心的方法之一,用于存储键值对。数组长度翻倍(如 16 → 32)。1、什么是 HashMap?数组 + 链表 / 红黑树。不支持同步,性能更高。键值对数量超过阈值(

2301_78566776的博客 628

java 8 Hashmap深入解析 —— put get 方法源码

java 8 Hashmap深入解析 —— put get 方法源码

浪丶荡 233

HashMap put get源码解析

hashmap put get 源码解析

日日留心的技术专栏 151

HashMap put()方法源码解析

HashMap put()方法总结: ①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容; ②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③; ③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals; ④.判断table[i] 是否为treeNode,即table[i] 是否是红

weixin_42418334的博客 517

hashMap源码解析-put

转载【https://blog.csdn.net/ptsx0607/article/details/68945883】 首先看看hashMapput方法的源码 public V put(K key, V value) { // 若“key为null”,则将该键值对添加到table[0]中。 //如果键为null的话,则hash值为0 ...

qq_36160057的博客 206

HashMap的get与put流程源码深度解析

计算哈希值:通过hash()方法计算key的哈希值初始化检查:如果table为空,则调用resize()初始化定位桶位置:通过(n-1) & hash计算数组下标(等价于hash % n,但效率更高--位运算比取模快处理冲突如果该位置为空,直接插入新节点如果key已存在,则覆盖value如果是树节点,调用红黑树的插入方法如果是链表,遍历链表并在尾部插入树化检查:链表长度≥8时可能转为红黑树扩容检查:size超过threshold则扩容计算哈希值:与put相同的hash()方法定位桶位置:同样的。

m0_74977981的博客 649

Java HashMap的get/put源码深度解析(JDK 1.8)

本文基于JDK 1.8源码深度解析HashMap的get/put核心流程。HashMap采用数组+链表+红黑树混合结构,通过扰动函数(h=hashCode())^(h>>>16)优化哈希分布。put流程包含哈希计算、桶定位、树化判断(链表≥8且容量≥64时转红黑树)、尾插法插入及扩容迁移(高低位拆分避免rehash);get流程通过(n-1)&hash定位桶后,按链表遍历或红黑树搜索(O(log n))查找节点。关键设计包括树化阈值权衡、位运算替代取模、扩容优化等,文末附最佳实践与高频面试题解析

JouJz的博客 1305
上一篇: HashMap源码面试题
下一篇: TreeMap和LinkedHashMap
竹下星空
博客等级 码龄10年 86粉丝 144原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值