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

HashMap的get方法是如何实现的
- ✔️典型解析
- ✔️拓展知识仓
- ✔️如何避免HashMap get方法的哈希重
- ✔️HashMap get方法的优缺点有哪些
- ✔️HashMap get方法的是线程安全的吗
- ✔️什么是ConcurrentHashMap
- ✔️ConcurrentHashMap有哪些应用场景
- ✔️ConcurrentHashMap的优缺点
- ✔️源码解读环节(每一行都加了注释方便快速透彻)
✔️典型解析
下面是JDK 1.8中HashMap的get方法的简要实现过程:
1 . 首先,需要计算键的哈希值,并通过哈希值计算出在数组中的索引位置
2 . 如果该位置上的元素为空,说明没有找到对应的键值对,直接返回null。
3 . 如果该位置上的元素不为空,遍历该位置上的元素,如果找到了与当前键相等的键值对,那么返回该键值对的值,否则返回null
具体来说,get方法首先计算出给定键的哈希值,然后使用这个哈希值在HashMap的内部数组中找到相应的位置。如果这个位置上有数据(即存在键值对),那么就返回该位置的值;如果这个位置上没有数据,那么就返回null。
看一个HashMap的get方法的简单实现:
public V get(Object key) { // 计算哈希值 int hash = hash(key); // 在内部数组中找到相应的位置 int index = indexFor(hash, table.length); // 判断该位置是否有数据 if (table[index] == null) { return null; // 该位置上没有数据 } else if (table[index].key == null) { return table[index].value; // 该位置上的键为null,直接返回值 } else if (key.equals(table[index].key)) { return table[index].value; // 找到了键值对,返回值 } else { return null; // 没找到键值对,返回null }
}
代码中,
hash方法用于计算给定键的哈希值,indexFor方法用于根据哈希值和内部数组的长度计算出相应的索引。如果找到了键值对,那么就返回该键值对的值;否则返回null。
✔️拓展知识仓
✔️如何避免HashMap get方法的哈希重
HashMap 的 get 方法在处理哈希冲突时,采用的是链地址法(Separate Chaining)。具体来说,当两个或更多的键计算出相同的哈希值时,它们会被放在同一个链表中。当 get 方法被调用时,首先计算出键的哈希值,然后在这个链表中查找相应的键。
要避免 HashMap 的 get 方法出现哈希冲突,可以考虑以下几点:
- 选择合适的哈希函数
在Java中,HashMap的默认哈希函数是Objects.hash(),它使用对象的各个字段进行哈希计算。如果可能,可以自定义一个哈希函数,以使键尽可能均匀地分布到内部数组中。
// 自定义哈希函数
public int customHash(String key) {// 自定义的哈希计算逻辑// ...return hash;
}
- 调整数组大小
当发现哈希冲突过多时,可以尝试调整内部数组的大小。这可以通过在创建HashMap时指定初始容量和负载因子来实现。
// 创建HashMap时指定初始容量和负载因子
HashMap<String, String> map = new HashMap<>(initialCapacity, loadFactor);
- 使用其他数据结构
如果发现HashMap的性能不佳,可以考虑使用其他数据结构,如TreeMap或LinkedHashMap。例如,TreeMap通过红黑树数据结构处理哈希冲突,LinkedHashMap通过维护一个运行于所有条目的双向链表来处理哈希冲突。
// 使用TreeMap代替HashMap
TreeMap<String, String> treeMap = new TreeMap<>();
treeMap.put("key", "value");
String value = treeMap.get("key"); // 获取值
- 避免使用频繁变动的键
如果一个对象的哈希码被改变,那么该对象就不再适合作为HashMap的键。因此,如果使用可变对象作为键,需要注意在修改对象后重新计算哈希码,或者使用其他方式来避免哈希冲突。例如,可以使用不可变对象作为键。
// 使用不可变对象作为键
public class ImmutableKey {private final int value;public ImmutableKey(int value) { this.value = value; }public int getValue() { return value; }// 重写hashCode和equals方法,保证不变性
}
ImmutableKey key = new ImmutableKey(123);
HashMap<ImmutableKey, String> map = new HashMap<>();
map.put(key, "value"); // 添加键值对
String value = map.get(key); // 获取值
以上是一些使用Java代码来避免HashMap get方法的哈希冲突的方法。根据具体的使用场景和需求,可以选择合适的方法来优化HashMap的性能。
✔️HashMap get方法的优缺点有哪些
HashMap的get方法具有以下优点和缺点:
优点:
1. 高效性:HashMap使用哈希表数据结构,通过哈希函数将键值对映射到数组的特定位置,使得查找、插入和删除操作的时间复杂度接近O(1)。
2. 灵活性:HashMap允许使用任何对象作为键和值,并且可以根据需要自定义哈希函数和比较器。这使得HashMap在数据结构方面非常灵活,可以适应不同的应用场景。
3. 无序性:HashMap中的键值对是无序的,即它们的存储和访问顺序无关。这使得HashMap在处理大量数据时能够提供稳定的性能表现。
缺点:
1. 哈希冲突:虽然HashMap使用哈希函数将键映射到数组的特定位置,但是由于不同的键可能具有相同的哈希值,因此仍然存在哈希冲突的问题。当哈希冲突较多时,会降低HashMap的性能。
2. 空间消耗:HashMap需要额外的空间来存储键值对以及用于维护哈希表的数据结构。因此,如果数据量很大,可能会占用较多的内存空间。
3. 线程不安全:HashMap不是线程安全的,如果在多线程环境下使用HashMap,可能会导致数据不一致或其他并发问题。需要使用额外的同步机制或者使用线程安全的替代品,例如ConcurrentHashMap。
✔️HashMap get方法的是线程安全的吗
HashMap的get方法不是线程安全的。在多线程环境下,如果多个线程同时访问HashMap,可能会引发数据不一致或其他并发问题。
如果需要在多线程环境下使用HashMap,可以考虑使用线程安全的替代品,例如ConcurrentHashMap。ConcurrentHashMap是Java中的一个线程安全哈希表实现,它使用分段锁技术来保证线程安全。与HashMap相比,ConcurrentHashMap在多线程环境下具有更好的性能表现。
另外,如果只是读操作而不涉及写操作,可以考虑使用只读集合,例如Collections.unmodifiableMap()方法返回的不可修改Map,或者使用线程安全的只读替代品,例如UnmodifiableConcurrentMap。这些只读集合或只读替代品可以避免不必要的同步开销,提高性能。
总之,在多线程环境下使用HashMap时需要注意线程安全问题,并选择合适的线程安全替代品或同步机制来保证数据的一致性和正确性。
看一个Demo吧:
/**
* HashMap的get方法在多线程环境下的不安全性
*/import java.util.HashMap; public class HashMapExample { public static void main(String[] args) { // 创建一个HashMap HashMap<String, String> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); // 创建两个线程,同时访问HashMap的get方法 Thread thread1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { String value = map.get("key1"); System.out.println("Thread 1: " + value); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { String value = map.get("key2"); System.out.println("Thread 2: " + value); } }); // 启动线程 thread1.start(); thread2.start(); }
}
在上面的Demo中,创建了一个HashMap并向其中添加了三个键值对。然后,创建了两个线程,每个线程都使用HashMap的get方法来获取键值对。由于
HashMap的get方法不是线程安全的,因此在多线程环境下,可能会出现数据不一致或其他并发问题。因此,在多线程环境下使用HashMap时,需要特别小心并考虑使用线程安全的替代品或同步机制来保证数据的一致性和正确性。
再看一个Demo吧:
/**
* 如何使用Java的并发工具类ConcurrentHashMap来替代HashMap,以确保线程安全
*/
import java.util.concurrent.ConcurrentHashMap; public class ConcurrentHashMapExample { public static void main(String[] args) { // 创建一个ConcurrentHashMap ConcurrentHashMap<String, String> concurrentMap = new ConcurrentHashMap<>(); concurrentMap.put("key1", "value1"); concurrentMap.put("key2", "value2"); concurrentMap.put("key3", "value3"); // 创建两个线程,同时访问ConcurrentHashMap的get方法 Thread thread1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { String value = concurrentMap.get("key1"); System.out.println("Thread 1: " + value); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { String value = concurrentMap.get("key2"); System.out.println("Thread 2: " + value); } }); // 启动线程 thread1.start(); thread2.start(); }
}
在这个Demo中,使用了
ConcurrentHashMap来替代HashMap。ConcurrentHashMap是Java中的一个线程安全哈希表实现,它使用分段锁技术来保证线程安全。因此,即使在多线程环境下,ConcurrentHashMap的get方法也是安全的,不会出现数据不一致或其他并发问题。通过使用ConcurrentHashMap,我们可以避免使用额外的同步机制或线程安全的替代品,从而简化代码并提高性能。
✔️什么是ConcurrentHashMap
ConcurrentHashMap是Java中的一个线程安全的哈希表实现,它是HashMap的一个并发版本。在多线程环境中,ConcurrentHashMap提供高效的并发访问。
ConcurrentHashMap在实现上采用了分段锁的机制,将整个哈希表分成多个段,每个段都有自己的锁。当多个线程同时访问ConcurrentHashMap时,它们只需要竞争某个段上的锁,而不是整个哈希表的锁,从而提高了并发性能。
因此,ConcurrentHashMap在并发编程中是一种常用的数据结构,用于在多线程环境下安全地进行数据访问和修改。
✔️ConcurrentHashMap有哪些应用场景
ConcurrentHashMap的应用场景主要包括需要高并发访问和修改的场景,尤其是在多线程环境下。以下是一些常见的应用场景:
- 共享数据存储:在高并发访问情况下,多个线程同时对数据进行读写操作,需要使用ConcurrentHashMap作为共享数据存储,以保证数据的一致性和正确性。
- 数据缓存:ConcurrentHashMap可以作为线程安全的缓存实现,用来存储经常访问的数据。多个线程可以同时读取缓存数据,而不会相互干扰。
- 任务调度:在多线程任务调度系统中,可以使用ConcurrentHashMap来存储和管理任务。线程可以安全地获取、修改和删除任务,而不会出现死锁或数据不一致的问题。
- 分布式系统:在分布式系统中,ConcurrentHashMap可以用于实现分布式锁或者分布式数据存储。多个节点可以同时访问和修改数据,而不会出现冲突或数据不一致的问题。
- 数据库连接池:数据库连接池可以使用ConcurrentHashMap来管理连接。多个线程可以同时获取、释放连接,而不会相互干扰。
- 事件驱动系统:在事件驱动系统中,ConcurrentHashMap可以用于存储事件和监听器。多个线程可以同时发布和消费事件,而不会出现数据不一致或死锁的问题。
需要注意的是,尽管ConcurrentHashMap提供了线程安全的并发访问,但在使用时仍需注意避免死循环和其他线程相关的问题。另外,如果只需要对数据进行单线程操作,则可以选择使用HashMap或其他非线程安全的数据结构,以提高性能。
✔️ConcurrentHashMap的优缺点
ConcurrentHashMap是Java中的一个线程安全的哈希表实现,它提供高效的并发访问。以下是一些ConcurrentHashMap的优缺点:
优点:
- 线程安全:ConcurrentHashMap在多线程环境下提供安全的访问和修改,不会出现数据不一致或死锁的问题。
- 高并发性能:通过分段锁的机制,ConcurrentHashMap能够支持高并发的读写操作,提高了系统的吞吐量和响应速度。
- 动态扩容:ConcurrentHashMap能够自动进行扩容,以适应数据量的增长。
- 易于使用:ConcurrentHashMap提供了丰富的API,方便进行数据的添加、删除、查找等操作。
缺点:
- 内存消耗较大:由于ConcurrentHashMap使用了额外的锁和数据结构来保证线程安全,因此相对于HashMap,其内存消耗会更大。
- 写操作性能略低:由于ConcurrentHashMap在写操作时需要获取锁,因此在高并发场景下,写操作的性能可能会略低于HashMap。
- 不支持完全有序:ConcurrentHashMap的迭代器并不保证元素的顺序,虽然其内部元素的顺序是确定的,但外部迭代器无法保证顺序。
- 不保证强一致性:在某些情况下,ConcurrentHashMap可能不会立即反映出所有的并发修改。虽然这通常不会导致问题,但在某些特定场景下可能需要额外的处理。
注意:尽管ConcurrentHashMap具有上述优点和缺点,但在实际应用中,其线程安全和高并发性能的优点往往更加突出,因此被广泛使用在需要高并发访问和修改的场景中。
✔️源码解读环节(每一行都加了注释方便快速透彻)
public V get(Object key) {Node<k,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;
}
get 方法看起来很简单,就是通过同样的 hash 得到 key 的hash 值。重点看下 getNode方法:
final Node<k,V> getNode(int hash, Object key) {//当前HashMap的散列表的引用Node<K,V>[] tab;//first:桶头元素//e: 用于存放临时元素Node<K,V> first, e;//n: table 数组的长度int n;//元素中的 kK k;// 将 table 赋值为 tab,不等于null 说明有数据,(n = tab.ength) > 同理说明 table 中有数据//同时将 该位置的元素 赋值为 firstif ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {//定位到了桶的到的位置的元素就是想要获取的 key 对应的,直接返回该元素if (first.hash == hash && ((k = first.key) == key ||(key != null && key.equals(k)))) {return first;}//到这一步说明定位到的元素不是想要的,且该位置不仅仅有一个元素,需要判断是链表还是树if ((e = first.next) != null) {//是否已经树化if (first instanceof TreeNode) {return ((TreeNode<K,V>) first).getTreeNode(hash, key);}//处理链表的情况do {//如果遍历到了就直接返回该元素if (e.hash == hash && ((k = e.key) == key ||(key != null 88 key.equals(k)))) {return e;}} while ((e = e.next) != null);}}//遍历不到返回nu11return null;
}
相关文章:
【Java集合篇】HashMap的get方法是如何实现的?
HashMap的get方法是如何实现的 ✔️典型解析✔️拓展知识仓✔️如何避免HashMap get方法的哈希重✔️HashMap get方法的优缺点有哪些✔️HashMap get方法的是线程安全的吗✔️什么是ConcurrentHashMap✔️ConcurrentHashMap有哪些应用场景✔️ConcurrentHashMap的优缺点 ✔️源…...
Java学习苦旅(二十二)——MapSet
本篇博客将详细讲解Map和Set。 文章目录 搜索概念模型 MapMap.Entry<K, V>Map的常用方法说明TreeMap和HashMap的区别 Set常用方法说明TreeSet和HashSet的区别 结尾 搜索 概念 Map和set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例…...
【Linux Shell】12. 文件包含
和其他语言一样,Shell 也可以包含外部脚本,这样可以很方便的封装一些公用的代码作为一个独立的文件。可以理解为在第2个文件中包含第1个文件,执行第1个文件的代码。 被包含的文件 不需要可执行权限 。Shell 文件包含的语法格式如下࿱…...
前端-基础 常用标签-超链接标签( 锚点链接 )
锚点链接 : 点击链接,可以快速定位到 页面中的某个位置 如果不好理解,讲一个例子,您就马上明白了 >>> 这个是 刘德华的百度百科 ,可以看到,页面里面有很多内容,那就得有个目录了 …...
2024--Django平台开发-基础信息(一)
一、前置知识点 - Python环境搭建 (Python解释器、Pycharm、环境变量等) - 基础语法(条件、循环、输入输出、编码等) - 数据类型(整型、布尔型、字符串、列表、字典、元组、集合等) - 函数(文件操作、返回值、参数、作用域等) - 面向对象 (类、对象、封装、继承、多态等)包和模…...
C++力扣题目--94,144,145二叉树递归遍历
思路 这次我们要好好谈一谈递归,为什么很多同学看递归算法都是“一看就会,一写就废”。 主要是对递归不成体系,没有方法论,每次写递归算法 ,都是靠玄学来写代码,代码能不能编过都靠运气。 本篇将介绍前后…...
开源游戏引擎:创造无限可能 | 开源专题 No.56
godotengine/godot Stars: 62.6k License: MIT Godot Engine 是一个功能强大的跨平台游戏引擎,可用于创建 2D 和 3D 游戏。它提供了一套全面的常见工具,让用户可以专注于制作游戏而不必重复造轮子。该引擎支持将游戏一键导出到多个平台上,包…...
MyBatisPlus学习一:快速入门
前言 前面快速学习了Mybatis,现在开始快速学习MyBatisPlus 学习教程: 黑马mybatis教程全套视频教程,2天Mybatis框架从入门到精通 黑马程序员最新MybatisPlus全套视频教程,4小时快速精通mybatis-plus框架 简介 MyBatisPlus 是…...
2024最新外贸建站:ChemiCloud主机购买使用及自建外贸独立站教程
随着电商平台竞争的加剧,许多外贸从业者意识到减少对平台依赖的重要性,并选择搭建自己的外贸独立站来获得更多的控制权和灵活性。即使是没有建站基础的新手,也可以通过学习建站来实现这一目标。下面是一个适用于新手的外贸建站教程࿰…...
校招社招,认知能力测验,③如何破解语言常识类测试题?
作为认知能力测评中的一个环节,语言常识类,是大概率的出现,不同的用人单位可能略有不同,语言是一切的基础,而常识则意味着我们的知识面的宽度。 语言常识类的测试,如果要说技巧?难说....更多的…...
了解一下InternLM2
大模型的出现和发展得益于增长的数据量、计算能力的提升以及算法优化等因素。这些模型在各种任务中展现出惊人的性能,比如自然语言处理、计算机视觉、语音识别等。这种模型通常采用深度神经网络结构,如 Transformer、BERT、GPT( Generative P…...
关于使用统一服务器,vscode和网页版jupyter notebook的交互问题
autodl 查看虚拟环境 在antodl上租借了一个服务器,通过在网页上运行jupyter notebook和在vscode中运行,发现环境都默认的是miniconda3。 conda info --envs 当然环境中所有的包都是一样的。 要查看当前虚拟环境中安装的所有包,可以使用以…...
Linux22.04系统安装显卡驱动,cuda,cudnn流程
1. 安装显卡驱动 ubuntu-drivers deices显示所有适配显卡的驱动型号,recommended为推荐安装 安装 sudo apt install nvidia-driver-440重启 sudo reboot验证 nvidia-smi2. 安装cuda 在 CUDA Toolkit 的下载页面选择系统版本和安装方式,下载并运行…...
【常考简答题】操作系统
目录 1、什么是进程 2、创建进程步骤 3、什么是死锁 4、死锁四个必要条件 5、什么是内存管理 6、内存管理功能 7、进程的三个基本状态转化图 8、操作系统为什么引入线程 9、什么是对换技术,好处是什么 10、DMA直接存取控制工作方式流程图 11、什么是假脱…...
Large Language Models Paper 分享
论文1: ChatGPTs One-year Anniversary: Are Open-Source Large Language Models Catching up? 简介 2022年11月,OpenAI发布了ChatGPT,这一事件在AI社区甚至全世界引起了轰动。首次,一个基于应用的AI聊天机器人能够提供有帮助、…...
微信小程序实战-01翻页时钟-1
文章目录 前言需求分析功能设计界面设计界面结构设计界面样式设计 逻辑设计 单页功能实现运行结果 前言 我经常在手机上用的一款app有一个功能是翻页时钟,基于之前学习的小程序相关的基础内容,我打算在微信小程序中也设计一个翻页时钟功能,J…...
BigDecimal的性能问题
BigDecimal 是 Java 中用于精确计算的数字类,它可以处理任意精度的小数运算。由于其精确性和灵活性,BigDecimal 在某些场景下可能会带来性能问题。 BigDecimal的性能问题 BigDecimal的性能问题主要源于以下几点: 内存占用:BigDec…...
Defi安全-Monox攻击事件Foundry复现
其它相关内容可见个人主页 Mono攻击事件的介绍见:Defi安全–Monox攻击事件分析–phalconetherscan 1. 前情提要和思路介绍 Monox使用单边池模型,创建的是代币-vCash交易对,添加流动性时,只需添加代币,即可进行任意代…...
大二上总结和寒假计划
👂 Start Again - Connor Price/Chloe Sagum - 单曲 - 网易云音乐 👂 年年 - 徐秉龙 - 单曲 - 网易云音乐 目录 🌼前言 👊成长 (1)情感 (2)运动 (3)穿搭…...
使用 pdfh5 实现 pdf 预览功能
1. 安装 npm install pdfh5 2. 使用 html部分: <div id"showPdf" style"width: 100%;"></div> js部分: <script> //合同展示组件 import Pdfh5 from pdfh5 //合同组件样式 import pdfh5/css/pdfh5.css expo…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
