细说 Java 集合之 Map
前言:本文基于JDK8
一、HashMap
1.1、hash方法
hash
方法是map中的基石,后续很多操作都依赖hash
方法;
下面是 jdk 7 中 hash方法,注意
hashSeed
这个扰动因子,该值随机,所以同一个 key 每次调用hash方法后得到的hash值都不一样。
- 该值存在就是为了增加随机性,避免链表过长,性能急剧下降;
- jdk 8 中 hash方法移除了扰动因子,因为底层数据结构链表过长的情况下会进行转换为红黑树来提升性能;
// jdk 7
final int hash(Object k) {int h = hashSeed; // 扰动因子if (0 != h && k instanceof String) {return sun.misc.Hashing.stringHash32((String) k);}h ^= k.hashCode();// 扰动因子 参与 hash 计算// This function ensures that hashCodes that differ only by// constant multiples at each bit position have a bounded// number of collisions (approximately 8 at default load factor).h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);
}
1.2、put方法
1.3、putVal方法
其中( n - 1 ) & hash
是对 hash % n
(n 是 2 的幂次方)的性能优化。
HashMap数组长度都是2的幂次方,就是为了方便上述操作,提高处理效率。
注意上图中操作:数组位置
i
空闲时,直接存入节点,多线程情况下会导致元素丢失,所以putVal
方法线程不安全。
上图提到了采用拉链法解决hash冲突,下面来看下具体内容:
链表尾插法
JDK 7 时,采用的是头插法,即下一个冲突的键值对会放在上一个键值对的前面。扩容的时候就有可能导致出现环形链表,造成死循环。
注意链表转化为红黑树阈值TREEIFY_THRESHOLD 为 8;
TREEIFY_THRESHOLD - 1的值为 7
- 为何减1:
插入新节点时,binCount表示插入前的链表长度。
例如:
插入第8个节点时,插入前链表长度为7(binCount=7),满足条件7 >= 7,触发树化。
插入完成后,链表实际长度为8,此时已超过阈值,需转换为红黑树。
jdk 7 拉链法只有链表,并无红黑树处理逻辑。
1.4、方法treeifyBin
数组长度小于64时采用扩容数组来减少hash冲突;否则转换为红黑树;
1.5、扩容resize
重点关注迁移数组链表(红黑树同理,不再赘述)中元素:
之所以可以达到迁移前后数组位置的上述变化,奥秘就在于:
- 数组长度为2的 n 次幂;
- key 的 hash 值固定;
1.6、get方法
1.7、getNode方法
1.8、节点
1.8.1、Node
1.8.2、TreeNode
LinkedHashMap.Entry在HashMap.Node基础上扩展了
before
和after
指针,形成双向链表以维护插入/访问顺序。而TreeNode继承它后,天然具备了维护双向链表的能力。这种设计允许在树化(链表转红黑树)和反树化(红黑树转链表)时,快速重构链表结构,避免重新遍历节点。
1.9、小节
- JDK1.8 HashMap 结构为:数组+链表/红黑树;
- 数组长度为2的 n 次幂,方便用位运算快速计算数组下标;
- 链表转换为红黑树前提2个:
- 链表长度达到8个;
- 数组长度达到64;
- 数组扩容后,链表元素(红黑树一样)分为2个链表:
- 一个链表位置在新老数组中下标一样;
- 另外一个位置为旧数组位置+ 旧数组长度;
- 计算时通过hash值 与旧数组长度进行位与(&)操作来区分;
- 这种优化依赖hash值稳定不变和数组长度为2的n次幂;
二、LinkedHashMap
LinkedHashMap 继承了HashMap,增加了元素的顺序控制;
- 插入顺序;
- 访问顺序;
- 头部最老;
- 尾部最年轻;
2.1、HashMap 对LinkedHashMap支持:
HashMap 中添加了一系列钩子方法,来支持LinkedHashMap。
2.2、get方法
2.3、afterNodeAccess方法
- 开启访问顺序控制时,将访问节点移动到链表尾部;
- 迭代访问LinkedHashMap时,后输出的元素最近被访问过;
2.4、removeEldestEntry方法
- 是否移除最老元素;
- 当访问顺序标识开启,同时覆写此方法,如下图注释,即可实现简易版LRU(Least Recently Used,最近最少使用)
- 通过 LinkedHashMap实现LRU
// 自定义 LinkedHashMap,限制最大容量并开启淘汰逻辑
public class LRUCache<K,V> extends LinkedHashMap<K,V> {private final int maxCapacity;public LRUCache(int maxCapacity) {super(maxCapacity, 0.75f, true); // accessOrder=true 维护访问顺序this.maxCapacity = maxCapacity;}@Overrideprotected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return size() > maxCapacity; // 超过容量时移除最久未访问的节点}
}
2.5、afterNodeInsertion方法
LRU中移除最老元素就是此方法做的。
该钩子方法在HashMap中
putVal
方法结束前会调用;
其中的
removeNode
方法是HashMap中的方法,该方法结束前会调用钩子方法afterNodeRemoval
,详见2.6小节。
2.6、afterNodeRemoval方法
双向链表删除节点
三、ConcurrentHashMap
相对于 HashMap
,ConcurrentHashMap
就是线程安全的 map,其中利用了锁分段的思想大大提高了并发的效率。
ConcurrentHashMap
中与HashMap
中一致的内容不再赘述。
1.8 版本舍弃了 1.7中的 segment,使用了 synchronized
和 CAS
无锁操作来保证 ConcurrentHashMap
的线程安全。
- 为什么不用 ReentrantLock 而是 synchronzied 呢?
实际上,synchronzied 做了很多的优化(包括偏向锁、轻量级锁、重量级锁),synchronized 相较于 ReentrantLock 的性能其实差不多。
3.1、hash方法
ConcurrentHashMap 中hash方法名变为spread
,与HashMap相比,增加了将hash值变为正数(严谨点,非负数)的操作。
hash值为非负数是为了配合3.2小节中的辅助节点。
- key 不允许为 null,所以这里没有必要判空;
3.2、节点
ConcurrentHashMap 中5种节点分为两类:
- 数据节点(hash值为非负数,通过方法
spread
计算):- Node:链表节点;
- TreeNode:红黑树节点;
- 辅助节点(不存放数据,hash值固定,且均为负数):
- TreeBin:用于指向红黑树根节点,hash值为
-2
; - ForwardingNode:数组扩容时使用,hash值为
-1
; - ReservationNode:占位节点,hash值为
-3
,方法computeIfAbsent
和compute
中使用该节点。
- TreeBin:用于指向红黑树根节点,hash值为
3.2.1、Node
3.2.2、TreeNode
3.2.3、TreeBin
该节点hash值固定为TREEBIN = -2
3.2.4、ForwardingNode
- 注意,构造方法中
MOVED
是该节点的hash值,固定为-1
- 成员变量nextTable就是扩容过程中,用于指向新的数组位置。
3.2.5、ReservationNode
注意,构造方法中RESERVED
是该节点的hash值,固定为 -3
该节点使用详见3.6小节
3.3、put方法
为啥不允许key 、value 为null ?
多线程环境下的歧义性问题:
- HashMap中允许,当get(key) 返回null 时,单线程情况下:
- 通过
containskey(key)
可以确认key是否存在,即 key 不存在,所以value返回null; 或key存在,value就是null;- ConcurrentHashMap中不允许,它是面向多线程的,当get(key) 返回null 时:
- 通过
containskey(key)
无法确认key一开始是否存在?
- 线程A执行get(key) 返回null;
- 线程B执行put(key)设置,key = null或者删除了key;
- 线程A执行
containskey(key)
返回啥;问题来了,key一开始不存在(对于A就应该返回false),后面被B设置了,实际返回true;或者一开始存在(对于A就应该返回true),value就是null,后面被B删除了,实际返回false;这key一开始到底存不存在?已经产生歧义了。- 为了避免歧义,ConcurrentHashMap直接禁止key、value为null的情况。
3.4、putVal方法
注意比HashMap中的putVal
方法多了的循环是为了配合CAS操作。
CAS 一般都会伴随自旋操作,具体细节参阅 自旋、CLH、AQS浅析;
- 拉链法解决hash冲突,链表尾插法插入(链表长度超过8,尝试转换为红黑树)或者红黑树插入;
- 链表尝试转换为红黑树时,和HashMap一致,数组长度小于64时,首选扩容;否则,转换。
- 解决冲突时,使用内置锁
synchronized
,锁的对象时数组i
位置的节点,锁的粒度就是1个节点。
- jdk 7 使用
ReentrantLock
,锁的对象是segment
,包含数组相邻位置多个节点。- 为了避免浪费空间去为每一个数组桶关联一个锁对象,所以使用数组桶中第一个元素作为一把锁。
- 注意下图判断链表和红黑树的判断方法
- hash 值(下图变量
fh
)>=0 是链表;因为红黑树的根节点被节点TreeBin
(hash值固定为 -2)指着。
3.5、get方法
注意上图红黑树查找时,如遇到数组正在扩容时,会先找到
ForwardingNode
节点,通过ForwardingNode
找到扩容后的新数组,在新数组中查找(类似递归了)。
3.6、computeIfAbsent方法
我们只关注key不存在的情况,数组对应位置(记为
i
)元素为null,线程A、B同时执行此逻辑:
- 新建占位节点
ReservationNode
,记为r
, 通过CAS操作将占位节点设置设置到 数组i
;- 既然CAS,就会有失败的可能,所以外侧通过循环来自旋;
- CAS成功了,占位节点就会设置到数组
i
位置;后续就可以break
,退出循环;- CAS失败了,进入下次循环,注意下次循环进来后,不会再次进入CAS逻辑了;
- CAS失败意味着数组位置
i
元素(图示f
)!= null
,因为被别的线程设置了;- 下次循环会进入图示下方的同步代码块,此处加锁的对象是
f
;
- 此处加锁应该没有疑问,就是防止链表修改并发产生错误;
问题来了:占位节点r
作用域就在else if
这个代码块,每个线程执行时创建的r
都不同,为啥对r
加锁?
加锁一定有并发场景,问题是此处
r
会有其它线程来争抢吗?
- 回到上面CAS失败的情况,假设线程A CAS失败,线程B CAS成功;
- 线程B CAS成功意味着:线程B之前对自己创建的
r
加锁成功了;- 线程A 会进入下次循环,走到图示下方的同步代码块;
- 此时,线程B还在图示上方同步块中执行;
- 那线程A此时读到的对象是
f
就是线程B创建的r
了,即f == r
;- 线程A无法加锁成功,进入阻塞状态;
- 因为占位节点
r
时临时节点,最终会被线程B替换为真正的节点Node;
综上,占位节点ReservationNode
和真正的节点替换作为一个原子操作,是不可以分开的,所以此处必须对占位节点ReservationNode
进行加锁。
四、TreeMap
TreeMap可以保持元素的自然顺序,所以使用时需要提供排序器或将key实现排序接口。
4.1、树节点Entry
- TreeMap中的Tree是红黑树;
- 时间复杂度O(log n);
4.2、put方法
4.3、get方法
4.4、getEntry方法
相关文章:

细说 Java 集合之 Map
前言:本文基于JDK8 一、HashMap 1.1、hash方法 hash方法是map中的基石,后续很多操作都依赖hash方法; 下面是 jdk 7 中 hash方法,注意hashSeed 这个扰动因子,该值随机,所以同一个 key 每次调用hash方法后…...
【vue-echarts】——05.柱状图
文章目录 一、柱状图基本设置1.实现代码2.结果展示二、柱状图效果实现11.代码实现2.结果展示三、柱状图效果实现21.代码实现2.结果展示一、柱状图基本设置 柱状图:一种图表类型,因为构成是由一根一根类似柱子的数据条组合而成的坐标平面,所以命名为柱状 图。主要是用来反应对…...

【C】链式二叉树算法题1 -- 单值二叉树
leetcode链接https://leetcode.cn/problems/univalued-binary-tree/description/ 1 题目描述 如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。只有给定的树是单值二叉树时,才返回 true;否则返回 false。 示例 1࿱…...

C++11——智能指针和function库
目录 一、智能指针 1. std::unique_ptr(独占所有权指针) 2. std::shared_ptr(共享所有权指针) 3. std::weak_ptr(弱引用指针) 关键区别总结 最佳实践 基本用法 可封装的对象类型 核心特性 示例代码…...

[操作系统] 文件的软链接和硬链接
文章目录 引言硬链接(Hard Link)什么是硬链接?硬链接的特性硬链接的用途 软链接(Symbolic Link)什么是软链接?软链接的特性软链接的用途 软硬链接对比文件的时间戳实际应用示例使用硬链接节省备份空间用软链…...

RabbitMQ面试题及原理
RabbitMQ使用场景: 异步发送(验证码、短信、邮件…)MYSQL和Redis, ES之间的数据同步分布式事务削峰填谷 1. 消息可靠性(不丢失) 消息丢失场景: RabbitMQ-如何保证消息不丟失? 开启生产者确…...
SpringBoot中Get请求和POST请求接收参数详解
1、Get请求 1.1 方法形参接收参数 这种方式一般适用参数比较少的情况,并且前后端参数名称必须保持一致 RestController RequestMapping(“/user”) Slf4j public class DemoController { GetMapping("/query") public void getStudent(String name,Strin…...

分布式日志和责任链路
目录 日志问题 责任链问题 分布式日志 GrayLog简介 部署安装 收集日志 配置Inputs 集成微服务 日志回收策略 搜索语法 搜索语法 自定义展示字段 日志统计仪表盘 创建仪表盘 链路追踪 APM 什么是APM 原理 技术选型 Skywalking简介 部署安装 微服务探针 整合…...

h5 IOS端渐变的兼容问题 渐变实现弧形效果
IOS端使用渐变的时候有兼容问题 以下是问题效果,图中黑色部分期望的效果应该是白色的。但是ios端是下面的样子…… 安卓pc 支持: background-image: radial-gradient(circle 40rpx at 100% 0, #f3630c 40rpx, rgb(255, 255, 255) 50%);安卓pc ios支持…...

哈希算法--猜数字游戏
1.题目要求 输入两个位数相同的数,判断对应位置的数字是否相等,返回两个数。第一个数是数字和位置完全猜对的数字个数,第二个数是数字大小猜对但位置不对的数字个数 2.逐步编程 2.1 定义函数 def g(secret,guess):sec_dic{}gue_dic{}# 定义…...

idea生成自定义Maven原型(archetype)项目工程模板
一、什么是Maven原型(Maven archetype) 引自官网的介绍如下: Maven原型插件官网地址 这里采用DeepSeek助手翻译如下: Maven 原型 什么是原型? 简而言之,原型是一个 Maven 项目模板工具包。原型被定义为一…...

Redis面试常见问题——使用场景问题
目录 Redis面试常见问题 如果发生了缓存穿透、击穿、雪崩,该如何解决? 缓存穿透 什么是布隆过滤器? 缓存击穿 缓存雪崩 双写一致性(redis做为缓存,mysql的数据如何与redis进行同步呢?) …...
样式和ui(待更新)
element-plus 先在项目下执行安装语句执行按需导入的命令按照官方文档修改vitest.json sass样式定制 npm -i sass -D在项目下准备定制的样式文件 styles/element/index.scss(!注意这里是.scss文件在vitest.json 修改配置文件 Components({resolvers: [ElementPlusResolver(…...

大摩闭门会:250228 学习总结报告
如果图片分辨率不足,可右键图片在新标签打开图片或者下载末尾源文件进行查看 本文只是针对视频做相应学术记录,进行学习讨论使用...

线程(Thread)
一、概念 线程:线程是一个轻量级的进程 二、线程的创建 1、线程的空间 (1)进程的空间包括:系统数据段、数据段、文本段 (2) 线程位于进程空间内部 (3) 栈区独享、与进程共享文本段、…...
AI军备竞赛2025:GPT-4.5的“情商革命”、文心4.5的开源突围与Trae的代码革命
AI军备竞赛2025:GPT-4.5的“情商革命”、文心4.5的开源突围与Trae的代码革命 ——一场重塑人类认知边界的技术战争 一、OpenAI的“感性觉醒”:GPT-4.5的颠覆与争议 1.1 从“冷面学霸”到“温柔导师”:AI的情商跃迁 当用户输入“朋友放鸽子&…...

DeepSeek + 自由职业 发现新大陆,从 0 到 1 全流程跑通商业 IP
DeepSeek 自由职业 发现新大陆,从 0 到 1 全流程跑通商业 IP 商业定位1. 商业定位分析提示词2. 私域引流策略提示词3. 变现模型计算器提示词4. 对标账号分析提示词5. 商业IP人设打造提示词6. 内容选题策略提示词7. 用户人群链分析提示词8. 内容布局与转化路径设计提…...
Java进阶——常用工具类
日常开发中,Arrays、Collections 和 Objects 是非常实用的工具类,提供了丰富的功能,从而可以更高效地处理数组、集合和对象。本文将详细介绍这三个工具类的重要知识细节。 本文目录 一、 Arrays数组转集合并行排序优化Stream 支持 二、 Colle…...

【考试大纲】高级系统架构设计师考试大纲
目录 引言一、 考试说明1.考试目标2.考试要求3.考试科目设置二、 考试范围考试科目1:系统架构设计综合知识考试科目2:系统架构设计案例分析考试科目3:系统架构设计论文引言 最新的系统架构设计师考试大纲出版于 2022 年 11 月,本考试大纲基于此版本整理。 一、 考试说明…...
上位机知识篇---四种CPU架构交叉编译
文章目录 前言一、四种 CPU 架构1. x86/x86_64指令集位宽:应用场景编译工具 2. ARM指令集位宽:应用场景编译工具 3. MIPS指令集位宽应用场景编译工具 4. RISC-V指令集位宽应用场景编译工具 二、交叉编译1. 什么是交叉编译?定义应用场景 2. 交…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...

网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...