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领…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
Ubuntu系统多网卡多相机IP设置方法
目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机,交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息,系统版本:Ubuntu22.04.5 LTS;内核版本…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...
嵌入式面试常问问题
以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...
SpringSecurity+vue通用权限系统
SpringSecurityvue通用权限系统 采用主流的技术栈实现,Mysql数据库,SpringBoot2Mybatis Plus后端,redis缓存,安全框架 SpringSecurity ,Vue3.2Element Plus实现后台管理。基于JWT技术实现前后端分离。项目开发同时采 …...
