【Java集合篇】HashMap的put方法是如何实现的?

在这里插入图片描述

HashMap的put方法是如何实现的

  • ✔️典型解析
  • ✔️ 拓展知识仓
    • ✔️HashMap put方法的优缺点有哪些
    • ✔️如何避免HashMap put方法的哈希冲突
    • ✔️如何避免HashMap put方法的哈希重
  • ✔️源码解读
    • ✔️putVal 方法主要实现如下,为了更好的帮助大家阅读,提升效率,每一行都特意加了注释


✔️典型解析


先看一个简化的put方法实现,帮助理解其基本逻辑:


public V put(K key, V value) {  // 1. 计算键的哈希码  int hash = hash(key);  // 2. 计算桶的位置  int index = indexFor(hash, table.length);  // 3. 检查桶中是否有键值对  for (Entry<K,V> e = table[index]; e != null; e = e.next) {  Object k;  if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {  // 如果找到相同的键,则更新值  return e.setValue(value);  }  }  // 4. 如果桶为空或者找到了空位,插入新的键值对  addEntry(hash, key, value, index);  return null; // 表示新插入的键值对,其值是null  
}

下面是JDK 1.8中HashMap的put方法的简要实现过程:


1 . 首先,put方法会计算键的哈希值(通过调用hash方法),并通过哈希值计算出在数组中的索引位置。


2 . 如果该位置上的元素为空,那么直接将键值对存储在该位置上.


3 . 如果该位置上的元素不为空,那么遍历该位置上的元素,如果找到了与当前键相等的键值对,那么将该键值对的值更新为当前值,并返回旧值。


4 . 如果该位置上的元素不为空,但没有与当前键相等的键值对,那么将键值对插入到链表或红黑树中(如果该位置上的元素数量超过了一个闻值,就会将链表转化为红黑树来提高效率)。


5 . 如果插入成功,返回被替换的值:如果插入失败,返回null.


6 . 插入成功后,如果需要扩容,那么就进行一次扩容操作。


✔️ 拓展知识仓


✔️HashMap put方法的优缺点有哪些


HashMap的put方法在实现上具有一些优点和缺点。以下是一些可能的优缺点:

优点:


  1. 高效性:HashMap使用哈希表作为其底层实现,使得插入、删除和查找操作的时间复杂度接近O(1)。这使得HashMap在处理大量数据时具有很高的效率。

  1. 易于使用:HashMap提供了简单的方法来存储和检索数据,如put和get方法。这些方法易于使用,并且可以快速地添加新数据或检索现有数据。
  2. 动态扩展:HashMap可以根据需要自动调整其大小,以适应不同数量的数据。这使得HashMap能够处理任意数量的数据,而无需手动管理内存。

缺点:

  1. 哈希冲突:如果两个键的哈希码相同,则会发生哈希冲突。虽然HashMap使用链表或红黑树来解决冲突,但哈希冲突可能导致性能下降,特别是在高负载情况下。

  1. 线程不安全:HashMap不是线程安全的。如果在多线程环境中使用HashMap,需要额外的同步机制来保证线程安全。这可能导致性能下降和代码复杂度增加。

  1. 内存消耗:HashMap使用内存来存储键值对和哈希表结构。如果存储大量数据,可能会占用大量内存。

以上是HashMap put方法的一些优缺点。在实际应用中,选择合适的数据结构非常重要,需要根据具体需求进行权衡和选择。


✔️如何避免HashMap put方法的哈希冲突


哈希冲突是HashMap实现中不可避免的问题,但是可以通过一些策略来减少其影响:


  1. 选择合适的哈希函数:哈希函数是将键映射到桶的函数,选择一个好的哈希函数可以减少哈希冲突的发生。尽可能选择能够均匀分布键的哈希函数,以减少冲突的可能性。

  1. 使用链表或红黑树:当发生哈希冲突时,HashMap可以使用链表或红黑树来存储具有相同哈希码的键值对。链表可以存储多个键值对,而红黑树则可以平衡树中的节点,提高查找效率。

  1. 调整数组大小:如果哈希冲突过多,可以考虑增加HashMap的数组大小。这将重新分配内存,并可能导致性能下降。因此,应该谨慎地调整数组大小,以平衡性能和内存使用。

  1. 使用开放地址法:开放地址法是一种解决哈希冲突的方法,其中当发生冲突时,会在哈希表中寻找下一个可用的位置来存储键值对。这种方法可以减少哈希冲突的发生,但也可能导致性能下降。

  1. 合理设置负载因子:负载因子是已存储的键值对数量与桶的数量之比。如果负载因子过高,可能会导致哈希冲突增多。合理设置负载因子可以平衡性能和内存使用。

避免HashMap put方法的哈希冲突需要选择合适的哈希函数、使用链表或红黑树、调整数组大小、使用开放地址法和合理设置负载因子等方法。在实际应用中,需要根据具体情况进行权衡和选择。


✔️如何避免HashMap put方法的哈希重


避免HashMap的哈希冲突的关键在于设计一个好的哈希函数,使得键的哈希码尽可能均匀分布,减少冲突的可能性。以下是一些避免哈希冲突的策略:

  1. 选择合适的哈希函数:尽可能选择能够均匀分布键的哈希函数。哈希函数的设计应该考虑键的特性,使得不同的键能够产生不同的哈希码。

  2. 使用链表或红黑树:当发生哈希冲突时,HashMap可以使用链表或红黑树来存储具有相同哈希码的键值对。链表可以存储多个键值对,而红黑树则可以平衡树中的节点,提高查找效率。

  3. 调整数组大小:如果哈希冲突过多,可以考虑增加HashMap的数组大小。这将重新分配内存,并可能导致性能下降。因此,应该谨慎地调整数组大小,以平衡性能和内存使用。


  1. 合理设置负载因子:负载因子是已存储的键值对数量与桶的数量之比。如果负载因子过高,可能会导致哈希冲突增多。合理设置负载因子可以平衡性能和内存使用。

  1. 使用再哈希策略:当发生哈希冲突时,可以考虑使用再哈希策略。即当发生冲突时,使用另一个哈希函数计算新的哈希码,并尝试在新的位置存储键值对。这种方法可以减少冲突的可能性,但也可能导致性能下降。


    综上所述,避免HashMap的哈希冲突需要选择合适的哈希函数、使用链表或红黑树、调整数组大小、合理设置负载因子和使用再哈希策略等方法。在实际应用中,需要根据具体情况进行权衡和选择。

看一个Demo:

/**
*   使用HashMap存储用户信息,并避免哈希冲突
*/import java.util.HashMap;public class UserInfo {private String username;private String email;private int age;public UserInfo(String username, String email, int age) {this.username = username;this.email = email;this.age = age;}// getter和setter方法省略@Overridepublic int hashCode() {int result = 17;result = 31 * result + username.hashCode();result = 31 * result + email.hashCode();result = 31 * result + age;return result;}@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (obj == null || getClass() != obj.getClass()) return false;UserInfo other = (UserInfo) obj;return age == other.age && username.equals(other.username) && email.equals(other.email);}
}

Demo中,定义了一个UserInfo类,用于存储用户信息。该类具有username、email和age属性,并实现了hashCode和equals方法。hashCode方法根据username、email和age计算哈希码,以确保不同的UserInfo对象具有不同的哈希码。equals方法用于比较两个UserInfo对象是否相等。

接下来,创建一个HashMap对象,用于存储UserInfo对象:

import java.util.HashMap;
import java.util.Map;public class UserDatabase {private Map<String, UserInfo> database;public UserDatabase() {database = new HashMap<>();}public void addUser(UserInfo user) {database.put(user.getUsername(), user);}public UserInfo getUser(String username) {return database.get(username);}
}

在Demo中,我们创建了一个UserDatabase类,用于管理用户信息。该类使用Map<String, UserInfo>作为底层实现,其中键是用户名,值是UserInfo对象。addUser方法用于添加用户信息,getUser方法用于根据用户名检索用户信息。注意,我们使用了UserInfo对象的username属性作为键,以确保不同的用户具有不同的键。这样,即使两个UserInfo对象的email和age属性相同,但由于键不同,它们仍然会被视为不同的键值对。这样可以减少哈希冲突的可能性。


✔️源码解读


put方法的代码很简单,就一行代码:


public V put(K key,V value) {return putVal(hash(key), key,value, false, true);
}

核心其实是通过 putValue 方法实现的,在传给 putValue 的参数中,先调用 hash 获取了一下hashCode


【Java集合篇】HashMap的hash方法是如何实现的?

✔️putVal 方法主要实现如下,为了更好的帮助大家阅读,提升效率,每一行都特意加了注释


/**
* Implements Map.put and related methods.
*   
* @param hash          key 的 hash 值
*  @param key          key 值
*  @param value        value 值
*  @param onlyIfAbsent true: 如果某个 key 已经存在那么就不插了; false 存在则替换,没有则新增。这里为 false
* @param evict         不用管了,我特喵也不认识
*  @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {// tab 表示当前 hash 散列表的引用Node<KV>[] tab;//表示具体的散列表中的元素Node<k,V> p;//n:表示散列表数组的长度//i:表示路由寻址的结果int n, i;// 将 table 赋值发给 tab ;如果 tab == nul,说明 table 还没有被初始化。则此时是需要去创建 table 的//为什么这个时候才创建散列表因为可能创建了 HashMap 时候可能并没有存放数据,如果在初始化 ahMap 的时候就创建散列表,势必会造成空间的浪费//这里也就是延迟初始化的逻辑if ((tab = table) == null  (n = tab.length) == 0)  {n = (tab = resize()).length;}//如p == null说明寻址到的桶的位置没有元素。那么就将 key-value 封装到 Node 中,并放到寻址到的下标为的位置if ((p = tabli = (n - 1) & hash]) == null) {tab[i] = newNode(hash, key, value, null);}//到这里说明 该位置已经有数据了,且此时可能是链表结构,也可能是树结构 else {// e 表示找到了一个与当前要插入的key value 一致的元素Node<k,V> e;// 临时的 keyK k;//p 的值就是上一步 f 中的结果即: 此时的 (p = tab[i = (n - 1) & hash]) 不等于 null// p 是原来的已经在  位置的元素,且新插入的 key 是等于 p中的key// 说明找到了和当前需要插入的元素相同的元素(其实就是需要替换而已)if (p.hash == hash && ((k = p.key) == key  (key != null && key.equals(k))))//将 p的值赋值给 ee =p;//说明已经树化,红黑树会有单独的文章介绍,本文不再整述else if ((p instanceof TreeNode) {e = ((Treelode<KV>) p).putTreeVal(this, tab, hash, key, value);} else {//到这里说明不是树结构,也不相等,那说明不是同一个元素,那就是链表了for (int binCount = 0; : ++binCount)  {//如果 p.next == nul1 说明 p 是最后一个元素,说明,该元素在链表中也没有重复的,那么就需要添加到链表的if ((e = p.next) == null)  {//直接将 key-value 封装到 Node 中并且添加到 p的后面p.next = newNode(hash , key, value, null);//当元素已经是 7了,再来一个就是 8 个了,那么就需要进行树化if (binCount >= TREEIFY_THRESHOLD - 1) {treeifyBin(tab, hash);}break;}//在链表中找到了某个和当前元素一样的元素,即需要做替换操作了。if (e.hash == hash && ((k = e.key) == key  (key != null && key.equals(k))))  {break:}//将e(即p.next)赋值为,这就是为了继续遍历链表的下一个元素(没啥好说的)下面有张图帮助大家理解p= e;}}//如果条件成立,说明找到了需要替换的数据,if (e != null)  {//这里不就是使用新的值赋值为旧的值嘛V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)  {e.value = value;}//这个方法没用,里面啥也没有afterNodeAccess(e)://HashMap put 方法的返回值是原来位置的元素值return oldValue;}}/***    上面说过,对于散列表的 结构修改次数,那么就修改 modCount 的次数*/++ modCount;//size 即散列表中的元素的个数,添加后需要自增,如果自增后的值大于扩容的阑值,那么就触发扩容操作if (++size > threshold) {resize();}//啥也没干afterNodeInsertion(evict);//原来位置没有值,那么就返回 nul1 呗return null;
}

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

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

相关文章

antd ColorPicker 颜色选择器

ColorPicker 属性 allowClear 允许清除选择的颜色 boolean false arrow 配置弹出的箭头 boolean | { pointAtCenter: boolean } true children 颜色选择器的触发器 React.ReactNode - defaultValue 颜色默认的值 string | Color - defaultFormat 颜色格式默认的值 rgb | he…

【独家解密】Java中定时任务的解决方案详解

目录 1、前言 2、定时任务的概述 2.1 什么是定时任务 2.2 定时任务的应用场景 3、使用Timer类和TimerTask类 3.1 Timer类的使用方法 3.2 TimerTask类的使用方法 4、使用ScheduledThreadPoolExecutor类 4.1 ScheduledThreadPoolExecutor类的使用方法 5、使用Spring框架…

ComfyUI报错AttributeError: module ‘cv2.gapi.wip.draw‘ has no attribute ‘Text‘

ComfyUI在安装comfyui-reactor-node插件,然后启动之后突然报错: AttributeError: module cv2.gapi.wip.draw has no attribute Text 这是怎么回事呢? 于是四处搜寻答案。 总之就是opencv-python版本的问题导致的。 我将有可能解决办法的方法进行了总结。 下面列出所有解…

操作系统实验二

实验二 观察Linux行为&#xff0c;使用proc文件系统 一、实验目的 学习Linux内核、进程、存储和其他资源的一些重要特征。读/proc/stat文件&#xff0c;计算并显示系统CPU占用率和用户态CPU占用率。&#xff08;编写一个程序使用/proc机制获得以及修改机器的各种资源参数。需要…

安卓上使用免费的地图OpenStreetMap

前一段使用了微信的地图&#xff0c;非常的好用。但是存在的问题是海外无法使用&#xff0c;出国就不能用了&#xff1b; 其实国内三家&#xff1a;百度&#xff0c;高德&#xff0c;微信都是一样的问题&#xff0c;当涉及到商业使用的时候需要付费&#xff1b; 国外除了谷歌…

51单片机介绍

1 单片机简介 单片机&#xff0c;英文Micro Controller Unit&#xff0c;简称MCU 内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能 单片机的任务是信息采集&#xff08;依靠传感器&#xff09;、处理&#xff08;依靠CPU&#xff09;和硬件设…

2.6 KERNEL LAUNCH

图2.15在vecAdd函数中显示最终主机代码。此源代码完成了图2.6.中的骨架。2.12和2.15共同说明了一个简单的CUDA程序&#xff0c;该程序由主机代码和设备内核组成。该代码是硬接的&#xff0c;每个线程块使用256个线程。然而&#xff0c;使用的线程块的数量取决于向量&#xff08…

jenkins通过流水线自动部署项目(k8s部署)

参考&#xff1a;https://www.cnblogs.com/rb2010/p/16195443.html docker 拉取镜像到本地&#xff1a; docker pull docker.io/jenkins/jenkins:2.164配置卷挂载&#xff1a;使用nfs 参考&#xff1a;https://www.kuboard.cn/learning/k8s-intermediate/persistent/nfs.htm…

指针传参误区

C语言中指针作为形参传递时&#xff0c;func&#xff08;*a, *b&#xff09; 这种形式的话&#xff0c;是无法通过简单的 ab来修改的&#xff0c;在函数体内a的地址确实被修改成b的地址了&#xff0c;但是当函数执行结束时&#xff0c;a的地址会重新回到原本的地址里面&#xf…

卷积神经网络|猫狗分类系列--导入kaggle猫狗数据集

解决任何真实问题的重要一步是获取数据&#xff0c;Kaggle提供了大量不同数据科学问题的竞赛。 我们将从 https://www.kaggle.com/competitions/dogs-vs-cats/data 下载猫狗数据集&#xff0c;并对其进行一定的操作&#xff0c;以正确的导入到我们的计算机&#xff0c;为接下…

四 视图

1、实验目的 理解SQL成熟设计基本规范&#xff0c;能够熟练使用SQL语句来创建需要的视图&#xff0c;定义数据库外模式&#xff0c;并能使用所创建的视图实现数据管理。 2、实验内容及要求 使用SQL对数据库进行各类查询数据操纵操作&#xff0c;掌握单行数据插入、多行数据插…

设计模式的艺术P1基础—第1章 概述

刘伟&#xff0c;2020 概述&#xff1a;4部分&#xff0c;26章。 P1:基础&#xff08;1-2章&#xff09; P2:创建型设计模式&#xff08;创建艺术&#xff0c;3-8章&#xff09; P3:结构型设计模式&#xff08;组合艺术&#xff0c;9-15章&#xff09; P4:行为型设计模式&…

2_工厂设计_工厂方法和抽象工厂

工厂设计模式-工厂方法 1.概念 工厂方法模式(Fatory Method Pattern ) 是指定义一个创建对象的接口&#xff0c;但让实现这个接口的类来决定实例化哪个类&#xff0c;工厂方法让类的实例化推迟到子类中进行。 在工厂方法模式中用户只需要关心所需产品对应的工厂&#xff0c;…

AUTOSAR开发文档

目录 目录 状态机电源管理开发... I 文档... I 1. 综述... 1 2. 系统硬件架构图... 1 3. 状态机设计方案... 2 4. 电源管理方案... 4 综述 本文档主要描述了MCU芯片TC297的AUTOSAR方案。MCU的基础软件由AUTOSAR软件实现&#xff0…

嵌入式——循环队列

循环队列 (Circular Queue) 是一种数据结构(或称环形队列、圆形队列)。它类似于普通队列,但是在循环队列中,当队列尾部到达数组的末尾时,它会从数组的开头重新开始。这种数据结构通常用于需要固定大小的队列,例如计算机内存中的缓冲区。循环队列可以通过数组或链表实现,…

微信小程序实战-01翻页时钟-1

文章目录 前言需求分析功能设计界面设计界面结构设计界面样式设计 逻辑设计 单页功能实现运行结果 前言 我经常在手机上用的一款app有一个功能是翻页时钟&#xff0c;基于之前学习的小程序相关的基础内容&#xff0c;我打算在微信小程序中也设计一个翻页时钟功能&#xff0c;J…

大模型实战营Day2 作业

基础作业 1 使用 InternLM-Chat-7B 模型生成 300 字的小故事 2 熟悉 hugging face 下载功能&#xff0c;使用 huggingface_hub python 包&#xff0c;下载 InternLM-20B 的 config.json 文件到本地 进阶作业 1 完成浦语灵笔的图文理解及创作部署 2 完成 Lagent 工具调用 Demo…

大数据计算基础真题回忆

转载学长20 21的真题 转载链接 注&#xff1a;每年的课件可能会有更改&#xff0c;内容不一样&#xff0c;所以读者复习的时候以所在年份的课件为准 2020 ​ 2021 笔者2023秋 2023 都是大题&#xff0c;没有选择题。 改进的近似算法中&#xff0c;结合具体的例子说明&am…

【Linux】Linux系统编程——Linux命令解析器

【Linux】Linux系统编程——Linux命令解析器 什么是Linux 命令解析器&#xff1f; Linux 命令解析器&#xff0c;通常被称为 shell&#xff0c;是 Linux 操作系统中的一个关键组件。它充当用户和系统内核之间的接口&#xff0c;允许用户通过输入命令来控制和管理操作系统和应…

计算机丢失mfc140.dll怎么办?解决mfc140.dll缺失的3种方法分享

计算机丢失mfc140.dll怎么办&#xff1f;在使用微软办公软件的时候&#xff0c;可能会弹出一个错误提示框说“找不到mfc140.dll&#xff0c;无法继续执行代码”。为了不影响工作效率&#xff0c;我们可能需要亲自动手尝试修复这一问题。以下是一些mfc140.dll缺失的3种方法相关介…