当前位置: 首页 > news >正文

细说 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基础上扩展了beforeafter指针,形成双向链表以维护插入/访问顺序。而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

相对于 HashMapConcurrentHashMap 就是线程安全的 map,其中利用了锁分段的思想大大提高了并发的效率

ConcurrentHashMap中与HashMap中一致的内容不再赘述。

1.8 版本舍弃了 1.7中的 segment,使用了 synchronizedCAS 无锁操作来保证 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,方法computeIfAbsentcompute中使用该节点。

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

前言&#xff1a;本文基于JDK8 一、HashMap 1.1、hash方法 hash方法是map中的基石&#xff0c;后续很多操作都依赖hash方法&#xff1b; 下面是 jdk 7 中 hash方法&#xff0c;注意hashSeed 这个扰动因子&#xff0c;该值随机&#xff0c;所以同一个 key 每次调用hash方法后…...

【vue-echarts】——05.柱状图

文章目录 一、柱状图基本设置1.实现代码2.结果展示二、柱状图效果实现11.代码实现2.结果展示三、柱状图效果实现21.代码实现2.结果展示一、柱状图基本设置 柱状图:一种图表类型,因为构成是由一根一根类似柱子的数据条组合而成的坐标平面,所以命名为柱状 图。主要是用来反应对…...

【C】链式二叉树算法题1 -- 单值二叉树

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

C++11——智能指针和function库

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

[操作系统] 文件的软链接和硬链接

文章目录 引言硬链接&#xff08;Hard Link&#xff09;什么是硬链接&#xff1f;硬链接的特性硬链接的用途 软链接&#xff08;Symbolic Link&#xff09;什么是软链接&#xff1f;软链接的特性软链接的用途 软硬链接对比文件的时间戳实际应用示例使用硬链接节省备份空间用软链…...

RabbitMQ面试题及原理

RabbitMQ使用场景&#xff1a; 异步发送&#xff08;验证码、短信、邮件…&#xff09;MYSQL和Redis, ES之间的数据同步分布式事务削峰填谷 1. 消息可靠性&#xff08;不丢失&#xff09; 消息丢失场景&#xff1a; RabbitMQ-如何保证消息不丟失&#xff1f; 开启生产者确…...

SpringBoot中Get请求和POST请求接收参数详解

1、Get请求 1.1 方法形参接收参数 这种方式一般适用参数比较少的情况&#xff0c;并且前后端参数名称必须保持一致 RestController RequestMapping(“/user”) Slf4j public class DemoController { GetMapping("/query") public void getStudent(String name,Strin…...

分布式日志和责任链路

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

h5 IOS端渐变的兼容问题 渐变实现弧形效果

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

哈希算法--猜数字游戏

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

idea生成自定义Maven原型(archetype)项目工程模板

一、什么是Maven原型&#xff08;Maven archetype&#xff09; 引自官网的介绍如下&#xff1a; Maven原型插件官网地址 这里采用DeepSeek助手翻译如下&#xff1a; Maven 原型 什么是原型&#xff1f; 简而言之&#xff0c;原型是一个 Maven 项目模板工具包。原型被定义为一…...

Redis面试常见问题——使用场景问题

目录 Redis面试常见问题 如果发生了缓存穿透、击穿、雪崩&#xff0c;该如何解决&#xff1f; 缓存穿透 什么是布隆过滤器&#xff1f; 缓存击穿 缓存雪崩 双写一致性&#xff08;redis做为缓存&#xff0c;mysql的数据如何与redis进行同步呢&#xff1f;&#xff09; …...

样式和ui(待更新)

element-plus 先在项目下执行安装语句执行按需导入的命令按照官方文档修改vitest.json sass样式定制 npm -i sass -D在项目下准备定制的样式文件 styles/element/index.scss(!注意这里是.scss文件在vitest.json 修改配置文件 Components({resolvers: [ElementPlusResolver(…...

大摩闭门会:250228 学习总结报告

如果图片分辨率不足&#xff0c;可右键图片在新标签打开图片或者下载末尾源文件进行查看 本文只是针对视频做相应学术记录&#xff0c;进行学习讨论使用...

线程(Thread)

一、概念 线程&#xff1a;线程是一个轻量级的进程 二、线程的创建 1、线程的空间 &#xff08;1&#xff09;进程的空间包括&#xff1a;系统数据段、数据段、文本段 &#xff08;2&#xff09; 线程位于进程空间内部 &#xff08;3&#xff09; 栈区独享、与进程共享文本段、…...

AI军备竞赛2025:GPT-4.5的“情商革命”、文心4.5的开源突围与Trae的代码革命

AI军备竞赛2025&#xff1a;GPT-4.5的“情商革命”、文心4.5的开源突围与Trae的代码革命 ——一场重塑人类认知边界的技术战争 一、OpenAI的“感性觉醒”&#xff1a;GPT-4.5的颠覆与争议 1.1 从“冷面学霸”到“温柔导师”&#xff1a;AI的情商跃迁 当用户输入“朋友放鸽子&…...

DeepSeek + 自由职业 发现新大陆,从 0 到 1 全流程跑通商业 IP

DeepSeek 自由职业 发现新大陆&#xff0c;从 0 到 1 全流程跑通商业 IP 商业定位1. 商业定位分析提示词2. 私域引流策略提示词3. 变现模型计算器提示词4. 对标账号分析提示词5. 商业IP人设打造提示词6. 内容选题策略提示词7. 用户人群链分析提示词8. 内容布局与转化路径设计提…...

Java进阶——常用工具类

日常开发中&#xff0c;Arrays、Collections 和 Objects 是非常实用的工具类&#xff0c;提供了丰富的功能&#xff0c;从而可以更高效地处理数组、集合和对象。本文将详细介绍这三个工具类的重要知识细节。 本文目录 一、 Arrays数组转集合并行排序优化Stream 支持 二、 Colle…...

【考试大纲】高级系统架构设计师考试大纲

目录 引言一、 考试说明1.考试目标2.考试要求3.考试科目设置二、 考试范围考试科目1:系统架构设计综合知识考试科目2:系统架构设计案例分析考试科目3:系统架构设计论文引言 最新的系统架构设计师考试大纲出版于 2022 年 11 月,本考试大纲基于此版本整理。 一、 考试说明…...

上位机知识篇---四种CPU架构交叉编译

文章目录 前言一、四种 CPU 架构1. x86/x86_64指令集位宽&#xff1a;应用场景编译工具 2. ARM指令集位宽&#xff1a;应用场景编译工具 3. MIPS指令集位宽应用场景编译工具 4. RISC-V指令集位宽应用场景编译工具 二、交叉编译1. 什么是交叉编译&#xff1f;定义应用场景 2. 交…...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

基于服务器使用 apt 安装、配置 Nginx

&#x1f9fe; 一、查看可安装的 Nginx 版本 首先&#xff0c;你可以运行以下命令查看可用版本&#xff1a; apt-cache madison nginx-core输出示例&#xff1a; nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...

6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙

Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...

篇章二 论坛系统——系统设计

目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...