HashMap源码
简介
HashMap 是一种基于哈希表的 Map 接口实现,它存储键值对(key-value pairs),并允许使用键来快速检索值。在 Java 中,HashMap 是 java.util 包的一部分,它不是同步的,这意味着它不是线程安全的。如果你需要线程安全的版本,可以使用 ConcurrentHashMap。
以下是 HashMap 的一些关键特性:
- 允许空键和空值:HashMap允许键和值为null。
- 非同步:HashMap不是线程安全的。如果你需要线程安全的HashMap,可以使用Collections.synchronizedMap方法包装一个HashMap,或者使用ConcurrentHashMap。
- 迭代顺序:从 JDK 1.8 开始,HashMap保证在并发访问的情况下,迭代顺序是可预测的,即按照插入顺序进行迭代。
- 初始容量和加载因子:HashMap可以指定初始容量和加载因子来优化性能。初始容量是哈希表中桶的数量,加载因子是一个影响哈希表扩展的阈值。
- 哈希冲突:当两个键的哈希码相同,或者哈希码经过数组索引计算后位置相同时,会发生哈希冲突。HashMap使用链表(在 JDK 1.8 之后是链表和红黑树的结合)来解决冲突。
以下是 Java 中 HashMap 的一个简单示例:
import java.util.HashMap;public class HashMapExample {public static void main(String[] args) {HashMap<String, Integer> map = new HashMap<>();map.put("apple", 1);map.put("banana", 2);map.put("cherry", 3);// 获取值Integer cherryValue = map.get("cherry");System.out.println("Cherry value: " + cherryValue);// 检查键是否存在if (map.containsKey("banana")) {System.out.println("Banana is in the map.");}// 删除键值对map.remove("apple");// 遍历 HashMapfor (Map.Entry<String, Integer> entry : map.entrySet()) {System.out.println(entry.getKey() + " : " + entry.getValue());}}
}
在这个示例中,我们创建了一个 HashMap 实例,并添加了一些键值对。然后我们获取了一个值,检查了一个键是否存在,删除了一个键值对,并遍历了 HashMap。
源码
hashmap成员变量
//创建 HashMap 时未指定初始容量情况下的默认容量,也就是16   static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //HashMap 的最大容量,也就是2的30次方=1 073 741 824static final int MAXIMUM_CAPACITY = 1 << 30;//HashMap 默认的装载因子,当 HashMap 中元素数量超过 容量*装载因子 时,进行resize()操作,也就是扩容static final float DEFAULT_LOAD_FACTOR =	 0.75f;//用来确定何时将解决 hash 冲突的链表转变为红黑树static final int TREEIFY_THRESHOLD = 8;// 用来确定何时将解决 hash 冲突的红黑树转变为链表static final int UNTREEIFY_THRESHOLD = 6;//当需要将解决 hash 冲突的链表转变为红黑树时,需要判断下此时数组容量,若是由于数组容量太小(小于MIN_TREEIFY_CAPACITY)导致的hash冲突太多,则不进行链表转变为红黑树操作,会先利用resize()函数对hashMap扩容static final int MIN_TREEIFY_CAPACITY = 64;// 这个就是hashMap的内部数组了,而Node则是链表节点对象。
transient Node<K,V>[] table;// 数组扩容阈值。
int threshold;
hashmap的节点的数据结构
    static class Node<K,V> implements Map.Entry<K,V> {//存储了键对象的哈希码,用于快速定位桶的位置final int hash;//存储键对象final K key;//存储与键相关联的值V value;//指向下一个节点的引用,用于解决哈希冲突,当链表长度好过一定阈值,列表会转成红黑树,以提高搜索效率Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}public final K getKey()        { return key; }public final V getValue()      { return value; }public final String toString() { return key + "=" + value; }public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (o == this)return true;if (o instanceof Map.Entry) {Map.Entry<?,?> e = (Map.Entry<?,?>)o;if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}}
hashmap的put方法
  public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}
 /*** Implements Map.put and related methods.** @param hash hash for key* @param key the key* @param value the value to put* @param onlyIfAbsent if true, don't change existing value 当存入键值对时,如果该key已存在,是否覆盖它的value。false为覆盖,true为不覆盖* @param evict if false, the table is in creation mode. 用于子类* @return previous value, or null if none*///final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab;//内部数组Node<K,V> p; //hash对应的索引中的首节点int n, i;//n 内部数组的长度  i hash对应的索引位//首次put时,内部数组为空,扩充数组if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//计算数组索引,计算该索引位置的首节点,如果为null,添加一个新节点if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;//如果首节点的key和要存入的key相同,那么直接覆盖value值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-1 ? 尝试转换红黑树 : 不转换if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//转换之前会先判断当前数组容量是否>=64 ? 转换红黑树 : resize扩容treeifyBin(tab, hash);break;}//检查链表中是否已包含keyif (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}//如果key存在则覆盖valueif (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}//fail-fast机制 // Java集合中的一种错误检测机制,当多个线程对集合进行结构性的改变时,有可能会出发fail-fast机制,这个时候会抛出ConcurrentModificationException异常。++modCount;//集合实际被修改的次数//如果集合容量大于阈值,则进行扩容if (++size > threshold)resize();afterNodeInsertion(evict);return null;}
hashmap的resize扩容操作
   final Node<K,V>[] resize() {//旧的数组 hashmap的内部数组Node<K,V>[] oldTab = table;//旧的数组长度 如果数组长度为空=0,不为空则是数组的长度int oldCap = (oldTab == null) ? 0 : oldTab.length;//旧的扩容阈值int oldThr = threshold;int newCap, newThr = 0;//旧的数组长度大于0if (oldCap > 0) {//旧的数组长度是否大于等于hashmap的最大容量if (oldCap >= MAXIMUM_CAPACITY) {//扩容阈值就是 int的最大值2的31次方-1threshold = Integer.MAX_VALUE;//此时已经是最大值了,不扩容直接返回旧数组return oldTab;}//新数组长度=2*旧数组长度 小于hashmap的最大容量 且旧数组长度大于默认值16 13644391557else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}//旧数组长度等于0 且旧的阈值大于0 新的初始数组容量被置为旧的阈值else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;else {               // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}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)//将一个红黑树节点分裂成两个子树。这个过程通常发生在并发环境下,当哈希表的大小超过某个阈值时,会将链表转换为红黑树以提高搜索效率。// 这里的 split 方法就是将一个红黑树节点分裂成两个子树,并将它们重新链接到哈希表的桶中。((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve orderNode<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;//根据节点的哈希值与旧容量 oldCap 的按位与操作的结果将它们分配到两个新的链表中。e.hash & oldCap 的结果决定了节点应该属于哪个新链表。do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;}
hashmap的get方法
    public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;}
get方法查找对应节点
    final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;//判断容器是否为空if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {//查看哈希值是否与首节点的哈希值相同if (first.hash == hash && // always check first node((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 && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;}
HashMap中的死锁
HashMap会造成死锁,因为HashMap是线程非安全的,并发的情况容易造成死锁,若要高并发推荐使用ConcurrentHashMap,这里的加了锁。
我们假设有二个进程T1、T2,HashMap容量为2,T1线程放入key A、B、C、D、E。在T1线程中A、B、C
Hash值相同,于是形成一个链,假设为A->C->B,而D、E
Hash值不同,于是容量不足,需要新建一个更大尺寸的hash表,然后把数据从老的Hash表中
迁移到新的Hash表中(refresh)。这时T2进程闯进来了,T1暂时挂起,T2进程也准备放入新的key,这时也
发现容量不足,也refresh一把。refresh之后原来的链表结构假设为C->A,之后T1进程继续执行,链接结构
为A->C,这时就形成A.next=B,B.next=A的环形链表。一旦取值进入这个环形链表就会陷入死循环。
死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法向前推进。
HashMap 可能导致死锁的情况通常与以下因素有关:
重入:当一个线程在扩容(rehashing)过程中,如果另一个线程尝试插入元素,可能会触发新的扩容操作。如果多个线程同时进行这样的操作,它们可能会相互等待对方释放锁,从而导致死锁。并发修改:如果多个线程同时尝试修改 HashMap 的结构(如插入或删除元素),而这些操作没有适当的同步控制,就可能导致死锁。不当的迭代:在多线程环境中,如果一个线程在迭代 HashMap 的同时,另一个线程修改了 HashMap,可能会导致迭代器抛出 ConcurrentModificationException。虽然这通常不会导致死锁,但如果迭代器的实现不当,或者在处理异常时没有正确地同步,也可能间接导致死锁。
为了避免 HashMap 的死锁问题,可以采取以下措施:
使用 ConcurrentHashMap:这是 Java 提供的一个线程安全的 HashMap 实现。它通过分段锁(segmentation)来允许并发的读写操作,从而避免了死锁。同步包装:可以使用 Collections.synchronizedMap 方法将 HashMap 包装为线程安全的 Map。但这种方式在高并发环境下性能可能不佳,因为它对整个 Map 加锁,而不是对单个元素。显式锁:可以使用 ReentrantLock 或 synchronized 块来手动管理对 HashMap 的访问,确保在修改 HashMap 时只有一个线程能够进行操作。不可变 Map:如果不需要修改 Map,可以使用 Collections.unmodifiableMap 创建一个不可修改的视图,这样即使在多线程环境下也不会出现死锁。避免在循环中进行结构修改:在循环中进行 HashMap 的结构修改(如扩容)可能会导致死锁,应该避免这种情况。使用读写锁:如果读操作远多于写操作,可以考虑使用读写锁(ReentrantReadWriteLock),它允许多个读线程同时访问,但写线程需要独占访问。
相关文章:
HashMap源码
简介 HashMap 是一种基于哈希表的 Map 接口实现,它存储键值对(key-value pairs),并允许使用键来快速检索值。在 Java 中,HashMap 是 java.util 包的一部分,它不是同步的,这意味着它不是线程安全…...
 
探索 Web Speech API:实现浏览器语音识别与合成
引言 Web Speech API 是一项由 W3C 开发的 Web 标准,为开发者提供了在 Web 应用程序中实现语音识别和语音合成的能力。通过 Web Speech API,我们可以让网页与用户进行语音交互,实现更加智能化和便捷的用户体验。本文将深入探讨 Web Speech A…...
python基础题练习
1.可否定义一个sum函数呢?返回指定区间的值的和?例如,区间[1,4]的和为123410返回指定区间值的平方的和呢?立方呢? 代码: # 计算从start到end(包括end)的所有整数的和。 def sum_ra…...
 
工业交换机如何保证数据的访问安全
在现代工业自动化环境中,工业交换机作为关键的网络设备,扮演着数据传输和信息交互的重要角色。为了确保数据的访问安全,工业交换机不仅具备高效的转发性能,还集成了多层次的安全防护机制,以抵御各种潜在的网络威胁。 首…...
 
jmeter得到的文档数据处理
通过前面jmeter得到的输出文档,这里是txt文档,里面包含了很多条数据,每条数据的结构如下: 【request】 uuid:xxxxxxx timestamp:xxxxxxxx No.x question:xxxxxxx 【response】 code&#…...
12- 【JavaWeb】校园快递管理系统-数据库建设
项目概述 开发一个Javaweb校园快递管理系统,包含以下功能: 数据库设计 首先,我们需要设计数据库的表结构。主要包括以下表: 学生表: 存储学生的基本信息,姓名、手机号。快递表: 存储快递的信息,快递单号、收件人、收件人手机号、…...
 
Windows本地连接远程服务器并创建新用户详细记录
前提可知: (1)服务器IP地址:x.x.x.x (2)服务器名称:root(一般默认为root,当然也有别的名称) (3)服务器登陆密码:**** 一、…...
 
【kaggle竞赛】毒蘑菇的二元预测题目相关信息和思路求解代码
毒蘑菇的二元预测 您提供了很多关于不同二元分类任务的资源和链接,看起来这些都是Kaggle竞赛中的参考资料和高分解决方案。为了帮助您更好地利用这些资源,这里是一些关键点的总结: Playground Season 4 Episode 8 主要关注的竞赛: 使用银行…...
Pytest-allure如何在测试完成后自动生成完整报告?
一、完整步骤 常规allure报告的生成方法是在pytest全部用例执行完成后,手动在命令行执行如 allure generate ./temps -o ./report --clean每次用例执行完成后都要重复如此的操作,十分繁琐。 可以使用如下方式让用例执行完成后自动生成报告到当前目录下…...
 
数据结构-树(基础,分类,遍历)
数据结构-树 1.什么是树? 在计算机科学中,树是一种常用的非线性数据结构,用于表示具有层次关系的数据。与线性数据结构(如数组和链表)不同,树结构以节点(Nodes)和边(Ed…...
 
CodeGeeX4:程序员的高效助手,多语言代码生成神器!
你是否曾在编写代码时,为复杂的语法、逻辑错误而绞尽脑汁?或是在面对多个编程语言的切换时,感觉脑子快要爆炸?别担心!一款全新的多语言代码生成神器——CodeGeeX4,正悄然成为程序员们的“救命稻草”。它不仅…...
 
小程序组件间通信
文章目录 父传子子传父获取组件实例兄弟通信 父传子 知识点: 父组件如果需要向子组件传递指定属性的数据,在 WXML 中需要使用数据绑定的方式 与普通的 WXML 模板类似,使用数据绑定,这样就可以向子组件的属性传递动态数据。 父…...
Homebrew安装与切换下载源
一、安装 1.Homebrew的官网地址 https://brew.sh/zh-cn/ 2.执行命令行安装 /bin/bash -c “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)” 3.无法连接到https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh的地址 解决…...
C#回调函数
1、定义并初始化委托 public delegate void CallbackDelegate(string message);//定义一个委托类型CallbackDelegate callbackDelegate;//声明一个委托对象/// <summary>/// 定义委托对应的函数/// </summary>/// <param name"str"></param>…...
 
Matplotlib绘制热力图
热力图(Heatmap)是一种使用颜色来表示数值强度的数据可视化工具。它常用于以下场景: 热力图的适用场景 数据的相关性分析:在统计学中,热力图常用于展示变量之间的相关性,尤其是当数据量较大时,…...
 
手写SpringMVC
1、开发HspDispatcherServlet 2、完成客户端/浏览器可以请求控制层 目的:发出url请求时,经过前端控制器,找到Monster的List方法,把结果再打回去 3、从web.xml动态获取hspspringmvc.xml 4、完成自定义Service注解功能 目的&…...
mysql学习教程,从入门到精通,SQL 删除数据(DELETE 语句)(18)
1、SQL 删除数据(DELETE 语句) 在编写SQL中的DELETE语句时,需要非常小心,因为一旦执行,被删除的数据就无法恢复了(除非你有备份)。DELETE语句用于从数据库表中移除一条或多条记录。这里&#x…...
周边游小程序开发
开发一个周边游小程序是一个既有趣又富有挑战性的项目,它可以帮助用户发现周边的旅游景点、活动、美食和住宿等,提升用户的旅游体验。以下是开发周边游小程序的基本步骤和一些建议: 1.市场调研与需求分析 目标用户定位:确定你的用…...
初级前端面试
1.介绍自己 2.介绍一下之前做过的项目以及接触的业务 3.最近学的技术,接触的是哪一块(回答了vue3) 4.vue3在什么时候调用接口 beforeCreate 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。 用…...
 
微软AI核电计划
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
 
DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
 
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
 
深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
 
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
 
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
