当前位置: 首页 > news >正文

HashMap的put、get方法详解(附源码)

put方法

HashMap 只提供了 put 用于添加元素,putVal 方法只是给 put 方法调用的一个方法,并没有提供给用户使用。
对 putVal 方法添加元素的分析如下:如果定位到的数组位置没有元素 就直接插入。如果定位到的数组位置有元素就和要插入的 key 比较,如果 key 相同就直接覆盖,如果 key 不相同,就判断 p 是否是一个树节点,如果是就调用e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value)将元素添加进入。如果不是就遍历链表插入(插入的是链表尾部)。
在这里插入图片描述
HashMap HashMap的put()方法用于向HashMap中添加键值对,当调用HashMap的put()方法时,会按照以下详细流程执行(JDK8 1.8版本):

第一步:根据要添加的键的哈希码计算在数组中的位置(索引)。
第二步:检查该位置是否为空(即没有键值对存在)

  • 如果为空,则直接在该位置创建一个新的Entry对象来存储键值对。将要添加的键值对作为该Entry的键和值,并保存在数组的对应位置。将HashMap的修改次数(modCount)加1,以便在进行迭代时发现并发修改。

第三步:如果该位置已经存在其他键值对,检查该位置的第一个键值对的哈希码和键是否与要添加的键值对相同?

  • 如果相同,则表示找到了相同的键,直接将新的值替换旧的值,完成更新操作。

第四步:如果第一个键值对的哈希码和键不相同,则需要遍历链表或红黑树来查找是否有相同的键:

如果键值对集合是链表结构,从链表的头部开始逐个比较键的哈希码和equals()方法,直到找到相同的键或达到链表末尾。

  • 如果找到了相同的键,则使用新的值取代旧的值,即更新键对应的值。
  • 如果没有找到相同的键,则将新的键值对添加到链表的头部。

如果键值对集合是红黑树结构,在红黑树中使用哈希码和equals()方法进行查找。根据键的哈希码,定位到红黑树中的某个节点,然后逐个比较键,直到找到相同的键或达到红黑树末尾。

  • 如果找到了相同的键,则使用新的值取代旧的值,即更新键对应的值。
  • 如果没有找到相同的键,则将新的键值对添加到红黑树中。

第五步:检查链表长度是否达到阈值(默认为8):

  • 如果链表长度超过阈值,且HashMap的数组长度大于等于64,则会将链表转换为红黑树,以提高查询效率。

第六步:检查负载因子是否超过阈值(默认为0.75):

  • 如果键值对的数量(size)与数组的长度的比值大于阈值,则需要进行扩容操作。

第七步:扩容操作:

  • 创建一个新的两倍大小的数组。
  • 将旧数组中的键值对重新计算哈希码并分配到新数组中的位置。
  • 更新HashMap的数组引用和阈值参数。

第八步:完成添加操作。
此外,HashMap是非线程安全的,如果在多线程环境下使用,需要采取额外的同步措施或使用线程安全的ConcurrentHashMap。

源码如下:

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;// table未初始化或者长度为0,进行扩容if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;// (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);// 桶中已经存在元素(处理hash冲突)else {Node<K,V> e; K k;//快速判断第一个节点table[i]的key是否与插入的key一样,若相同就直接使用插入的值p替换掉旧的值e。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);// 结点数量达到阈值(默认为 8 ),执行 treeifyBin 方法// 这个方法会根据 HashMap 数组来决定是否转换为红黑树。// 只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是对数组扩容。if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);// 跳出循环break;}// 判断链表中结点的key值与插入的元素的key值是否相等if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))// 相等,跳出循环break;// 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表p = e;}}// 表示在桶中找到key值、hash值与插入元素相等的结点if (e != null) {// 记录e的valueV oldValue = e.value;// onlyIfAbsent为false或者旧值为nullif (!onlyIfAbsent || oldValue == null)//用新值替换旧值e.value = value;// 访问后回调afterNodeAccess(e);// 返回旧值return oldValue;}}// 结构性修改++modCount;// 实际大小大于阈值则扩容if (++size > threshold)resize();// 插入后回调afterNodeInsertion(evict);return null;
}

get方法

HashMap的get(Object key)方法用于根据指定的键(key)获取其对应的值(value)。当调用此方法时,会执行与put()方法类似的定位逻辑来查找节点,但过程相对简单,因为它不涉及修改操作。以下是详细的执行流程(JDK 8 1.8版本):

第一步:计算哈希值,定位数组索引

  • 首先,get(key)方法会调用内部的getNode(hash(key), key)方法。
  • getNode方法中,它会根据传入的key计算出其哈希码(hash value)。
  • 然后,通过位运算 (n - 1) & hash(其中n是HashMap内部数组的长度)来精确计算出该key应该位于数组的哪个索引位置(即哪个桶)。

第二步:检查桶内情况,优先检查第一个节点。

  • 定位到数组索引后,系统会检查该位置是否存在节点。
  • 如果该位置为null(即桶为空),则意味着Map中不存在这个keygetNode方法直接返回null
  • 如果该位置不为null,系统会进行一个快速判断:检查该桶中第一个节点的哈希值和key本身是否与要查找的key完全匹配(先比较hash,再用==equals()比较key)。
  • 如果第一个节点就匹配成功,则直接返回这个节点,查找结束。这是对无哈希冲突情况的优化。

第三步:处理哈希冲突,遍历链表或红黑树。

  • 如果第一个节点不匹配,并且该节点后面还存在其他节点(即first.next != null),则说明发生了哈希冲突,需要进一步在链表或红黑树中查找。
  • 判断数据结构:
    • 如果为红黑树:通过 first instanceof TreeNode 判断。如果是,则调用红黑树专属的getTreeNode()方法,在树中进行高效查找。
    • 如果为链表: 则从第二个节点开始,进入一个do-while循环,逐个向后遍历链表中的每个节点。
  • 遍历查找:
    • 在遍历链表或查找红黑树的过程中,对每个节点都进行哈希值和key的比较。
    • 如果在链表或红黑树中找到了完全匹配的节点,则立即返回该节点。
    • 如果遍历完整个链表或红黑树仍未找到匹配项,getNode方法将返回null

第四步:返回最终结果。

  • get方法会接收getNode方法的返回值(一个Node对象或null)。
  • 如果返回的节点对象不为nullget方法会提取并返回该节点的value值。
  • 如果返回的节点为null,则get方法最终也返回null,表示在HashMap中未找到指定的key

源码如下:

// get方法是getNode方法的封装,它处理了getNode返回null的情况
public V get(Object key) {Node<K,V> e;// 调用getNode获取节点,如果节点为null,则返回null,否则返回节点的valuereturn (e = getNode(hash(key), key)) == null ? null : e.value;
}/*** 实现Map.get()和相关方法*/
final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;// 1. 检查table是否初始化,长度是否大于0,以及根据hash计算出的位置上是否有节点if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {// 2. 检查第一个节点,如果匹配,直接返回if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;// 3. 如果第一个节点不匹配,且存在后续节点if ((e = first.next) != null) {// 3.1 如果是红黑树,调用getTreeNode在树中查找if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);// 3.2 如果是链表,遍历链表查找do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}// 4. 如果table为空,或桶为空,或遍历完没找到,则返回nullreturn null;
}

参考链接:https://xiaolincoding.com/interview/collections.html#hashmap%E7%9A%84put%E8%BF%87%E7%A8%8B%E4%BB%8B%E7%BB%8D%E4%B8%80%E4%B8%8B

http://www.dtcms.com/a/270872.html

相关文章:

  • 冷冻电镜重构的GPU加速破局:从Relion到CryoSPARC的并行重构算法
  • 【前端】异步任务风控验证与轮询机制技术方案(通用笔记版)
  • 在Centos系统上如何有效删除文件和目录的指令汇总
  • 【C++ 】第二章——类(Class)学习笔记
  • SpringGateway网关增加https证书验证
  • 基于YOLO的足球检测Web应用:从训练到部署的完整实战
  • 《心灵沟通小平台,创新发展大未来》
  • brainstorm MEG处理流程
  • 2024 睿抗编程技能赛——省赛真题解析(含C++源码)
  • 图像匹配方向最新论文--CoMatch: Covisibility-Aware Transformer for Subpixel Matching
  • 【QT】文件、多线程、网络相关内容
  • 【基础算法】贪心 (四) :区间问题
  • spring-data-jpa + Alibaba Druid多数据源案例
  • (5)机器学习小白入门 YOLOv:数据需求与图像不足应对策略
  • OpenCV图片操作100例:从入门到精通指南(4)
  • [C#/.NET] 内网开发中如何使用 System.Text.Json 实现 JSON 解析(无需 NuGet)
  • 树莓派vsftpd文件传输服务器的配置方法
  • Java 大视界 -- 基于 Java 的大数据分布式计算在生物信息学蛋白质 - 蛋白质相互作用预测中的应用(340)
  • 【算法深练】DFS题型拆解:沿着路径“深挖到底”、递归深入、回溯回探的算法解题思路
  • 【数据分析】多数据集网络分析:探索健康与退休研究中的变量关系
  • ESOP系统电子作业指导汽车零部件车间的数字化革命
  • 玛哈特网板矫平机:精密矫平金属开平板的利器
  • 钉钉企业应用开发技巧:查询表单实例数据新版SDK指南
  • 2023年华为杯研究生数学建模竞赛A题WLAN组网分析
  • 结构体指针:使用结构体指针访问和修改结构体成员。
  • 【网络】Linux 内核优化实战 - net.ipv4.tcp_ecn_fallback
  • softmax
  • GitHub 趋势日报 (2025年07月08日)
  • SQLZoo 练习与测试答案汇总(复杂题有最优解与其他解法分析、解题技巧)
  • 分类预测 | Matlab基于KPCA-ISSA-SVM和ISSA-SVM和SSA-SVM和SVM多模型分类预测对比