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领…...

图片马赛克处理(Java)
1.需求 给图片的指定区域打码给整张图片打码马赛克方格取色支持中心点取色和随机取色马赛克支持灰度处理 2.源码 package com.visy.utils;import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOE…...

python+selenium实现自动联网认证,并实现断网重连
pythonselenium实现自动联网认证,并实现断网重连 echo off python “E:\autoD\auto_login.py” 要使自动登录脚本在系统重启后自动运行,你可以使用Windows的任务计划程序来设置。以下是详细的步骤: 1. 保存脚本 首先,将你的Py…...

基于机器学习的注意力缺陷/多动障碍 (ADHD)(python论文+代码)HYPERAKTIV
简述 医疗保健领域的机器学习研究往往缺乏完全可重复性和可比性所需的公共数据。由于患者相关数据附带的隐私问题和法律要求,数据集往往受到限制。因此,许多算法和模型发表在同一主题上,没有一个标准的基准。因此,本文提出了一个公…...

Spring Boot 集成 Redisson 实现消息队列
包含组件内容 RedisQueue:消息队列监听标识RedisQueueInit:Redis队列监听器RedisQueueListener:Redis消息队列监听实现RedisQueueService:Redis消息队列服务工具 代码实现 RedisQueue import java.lang.annotation.ElementTyp…...

go语言Map详解
Map Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现 map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。 它提供了高效的查找、插入和删除操作,非常适…...

C++——已知数组a[6]={1,3,5,7,9};输入一个数值,要求按照现有排序规律将它放入数组当中。
没注释的源代码 #include <iostream> using namespace std; int main() { int a[6]{1,3,5,7,9}; int n,i,j; cout<<"请输入一个数值:"; cin>>n; for(int i0;i<4;i) { if(n<a[i]) { …...

云计算第四阶段---CLOUD Day7---Day8
CLOUD 07 一、Dockerfile详细解析 指令说明FROM指定基础镜像(唯一)RUN在容器内执行命令,可以写多条ADD把文件拷贝到容器内,如果文件是 tar.xx 格式,会自动解压COPY把文件拷贝到容器内,不会自动解压ENV设置…...

深入解析ThingsBoard与ThingsKit物联网平台的差异
VS 在物联网(IoT)领域,平台的选择对于企业来说至关重要。本文将深入探讨ThingsBoard社区版与ThingsKit企业版这两个物联网平台的差异,帮助读者更好地理解它们的特色和适用场景。 系统相同点 首先,ThingsBoard社区版和ThingsKit企业版都基于…...

五、CAN总线
目录 一、基础知识 1、can介绍 2、CAN硬件电路 3、CAN电平标准 4、CAN收发器芯片介绍 5、CAN帧格式 ① CAN帧种类 ② CAN数据帧 ③ CAN遥控帧编辑 ④ 位填充 ⑤ 波形实例 6、接收方数据采样 ① 接收方数据采样遇到的问题 ② 位时序 ③ 硬同步 ④ 再同步 ⑤ 波…...

Linux:终端(terminal)与终端管理器(agetty)
终端的设备文件 打开/dev目录可以发现其中有许多字符设备文件,例如对于我的RedHat操作系统,拥有tty0到tty59,它们是操作系统提供的终端设备。对于tty1-tty12使用ctrlaltF*可以进行快捷切换,下面的命令可以进行通用切换。 sudo ch…...