目录

一.HashMap

1.基本概念

2.底层数据结构:

3.HashCode和equals方法

为什么重写HashCode方法?

为什么重新equals方法?

4.put操作

1.初始化和数组检查

2.计算索引并检查桶是否为空

3.桶不为null,处理哈希冲突

4.判断链表是否转化红黑树

5.以及数组大小是否超过阈值进行扩容

5.get操作

java1.7 -- java1.8 HashMap 做了哪些优化:

哈希值计算:

链表插入方式:

二.ConcurrentHashMap

1.Java 1.7 ConcurrentHashMap架构:

2.Java 1.8 ConcurrentHashMap架构:

CAS+synchronized的使用场景:

扩容区别:

size()区别:


一.HashMap

1.基本概念

HashMap是基于哈希表实现的Map接口,用于存储键值对(K-V)格式,其核心作用就是通过哈希函数为了更快查询到某个元素,时间复杂度O(1);

2.底层数据结构:

在java(1.7)之前 底层是采用“数组 + 链表” 的形式存储

但在java(1.8) 之后,变成了“数组 + 链表 + 红黑树 ”形式存储

我们主要了解java1.8之后的内容;

从源码上查看

数组的初始容量为16,负载因子为0.75,所以当超过这个阈值 16 * 0.75 = 12 
设计初始容量为16
核心原因是2 的幂次方更适合哈希计算,能减少哈希冲突,但8又太频繁扩容,新增性能开销,而32空间又会太大,则浪费空间~
负载因子
也是同理,太小,会频繁扩容,虽然查询快了,但是数组太稀疏,浪费空间,而太大,就会扩容少,空间利用率高,但查询会变慢~


当插入第13个元素的时候,已经超过阈值,为了避免哈希冲突,会进行扩容~

然后这边当数组大小超过64时候,链表大小超过8的时候,会从链表进化成红黑树,但当元素个数少于6个时,会退回链表~

  1. 链表长度≥8
    基于泊松分布,当负载因子为 0.75 时,链表长度自然增长到 8 的概率极低(约千万分之一),此时说明哈希冲突异常频繁,需要转为红黑树优化。

  2. 数组容量≥64
    若数组容量小于 64(如 32),即使链表长度≥8,也会先触发扩容(而非转红黑树)。原因是:数组容量小时,扩容成本低,通过扩容可分散元素,减少冲突;而红黑树的维护成本(插入 / 删除时的旋转操作)高于扩容,此时扩容更高效。

3.HashCode和equals方法

HashMap中的键一定会实现这俩个方法,hashCode用于计算存储的位置,而equals用于判断来个键是否相同,在put方法的时候,如果俩个哈希值相同,会判断是否是同一个值,如果不是就会放在同一个桶中的不同位置,如果相同就是一个东西~

为什么重写HashCode方法?

如果不重写,这个时候,就会导致俩个相同的key,会被计算出俩个不同的哈希值,导致他们存在在不同的桶中,到时候查询的时候到底是查哪个?

为什么重新equals方法?

equals方法是为了当出现哈希冲突的时候,俩个key的哈希值相同,放在同一桶中,但没有比较它们的对象,可能会误判会导致俩个key存储在不同位置,所以没有覆盖之前的值,就会错误~

class Person {String name;int age;@Overridepublic int hashCode() { // 重写hashCode,保证逻辑相等的对象哈希值相同return Objects.hash(name, age);}// 未重写equals()
}public static void main(String[] args) {HashMap<Person, String> map = new HashMap<>();Person p1 = new Person("张三", 20);Person p2 = new Person("张三", 20);map.put(p1, "学生");map.put(p2, "工人"); // 哈希值相同,进入同一个桶,但equals判断为不同keySystem.out.println(map.size()); // 仍输出2,而非预期的1
}

hashCode()equals()必须配套重写,且满足以下规则:

  • 一致性:如果两个对象通过equals()判断为相等,则它们的hashCode()必须返回相同的值;
  • 对称性:如果a.equals(b)true,则b.equals(a)也必须为true
  • 非空性:a.equals(null)必须返回false

4.put操作

1.初始化和数组检查

首先判断 HashMap 是否未初始化(table 数组为 null 或长度为 0),若是则触发 初始化(resize):

2.计算索引并检查桶是否为空

    如果该位置为空,直接创建新节点插入

    3.桶不为null,处理哈希冲突

    1. 检查首节点:若首节点的 key 与传入 key equals 相等(且哈希值相同),则视为 “重复 key”,记录该节点(后续替换其 value)。
    2. 遍历后续节点
      • 若桶是单链表(Node):遍历链表,若找到 key 相等的节点,记录该节点;若遍历到链表尾部仍未找到,则在链尾插入新节点(Node)。
      • 若桶是红黑树(TreeNode):调用红黑树的插入方法(putTreeVal),若找到重复 key 则记录节点,否则插入新树节点。
    3. 处理重复 key:若步骤 1 或 2 中找到重复 key 的节点,用新 value 替换其旧 value,流程结束。

    4.判断链表是否转化红黑树

    5.以及数组大小是否超过阈值进行扩容

    5.get操作

    同理get 方法的核心逻辑是:
    哈希计算→定位桶→根据桶结构(链表 / 红黑树)查找匹配节点

    java1.7 -- java1.8 HashMap 做了哪些优化:

    哈希值计算:

    1.7版本:对 key 的原始哈希值(hashCode() 结果)进行 4 次扰动(多次移位和异或运算)

    1.8版本:简化为 1 次扰动,仅通过一次 “高 16 位与低 16 位异或” 实现混合

    减少计算开销:一次异或运算即可达到近似的混合效果,相比 1.7 的 4 次运算,计算效率更高。
    实际效果:在大多数场景下,1 次扰动已能保证哈希值的均匀分布,同时降低了 CPU 运算成本。

    链表插入方式:

    由头插变成尾插,核心是为了解决多线程扩容时的链表环化问题,同时让链表操作逻辑更合理。

    多线程扩容时可能导致链表环化(死循环)
    扩容过程中,节点会从旧数组迁移到新数组,头插法在迁移时会反转链表顺序(例如旧链表 A→B,迁移后新链表变为 B→A)。若此时有多个线程同时操作,可能出现节点引用相互指向的情况(如 A.next = B 且 B.next = A),形成环形链表。后续查询时,线程会陷入无限循环,导致 CPU 占用飙升。


    二.ConcurrentHashMap

    ConcurrentHashMap 是 Java 中用于并发场景的哈希表实现,专为高并发环境设计;

    1.Java 1.7 ConcurrentHashMap架构:

    在java1.8之前 ConcurrentHashMap 采用的是 分段数组(Segment)+ 哈希表 , 默认为16个segment,同时支持16个并发~

    • 整体结构:ConcurrentHashMap -> Segment[] -> HashEntry[] -> 链表
    • 写操作(put/remove 等)
      1. 计算 key 的哈希值,定位到对应的 Segment
      2. 获取该 Segment 的锁(若被占用则阻塞);
      3. 在 Segment 内部的哈希表中执行插入 / 删除(类似 HashMap 的逻辑,链表头插法);
      4. 释放锁。
    • 优点:通过分段锁实现了多线程并发写,效率比全表锁(如 Hashtable)高得多。
    • 缺点
      • 锁粒度仍较大(以 Segment 为单位),若多个线程操作同一 Segment,仍会阻塞;
      • 结构复杂,维护成本高;
      • 扩容仅针对单个 Segment,但整体性能受限于 Segment 数量。

    2.Java 1.8 ConcurrentHashMap架构:

    摒弃了 Segment 分段结构,底层直接使用 数组 + 链表 + 红黑树(与 JDK 1.8 HashMap 结构类似)

    锁的粒度更小,锁的是桶的头节点,并且采取了CAS + synchronized 机制,仅当多个线程操作同一链表 / 红黑树时才会竞争锁,锁冲突概率远低于 1.7 的 Segment 级锁。

    CAS+synchronized的使用场景:

    1. 无冲突场景(空桶插入、初始化、低并发计数):用 CAS 实现高效无锁操作。
    2. 有冲突场景(非空桶操作、复杂结构修改):用 synchronized 加锁保证原子性。
    版本底层架构哈希表数量锁粒度
    JDK 1.7 及之前两层结构:Segment [] 数组 + 每个 Segment 包含一个 HashEntry [] 数组多个(默认 16 个,与 Segment 数量一致)Segment 级(锁整个子哈希表)
    JDK 1.8 及之后单层结构:Node [] 数组(链表 / 红黑树解决冲突)1 个(整个 ConcurrentHashMap 共用一个底层数组)节点级(锁链表头节点或红黑树根节点)

    扩容区别:

    特性JDK 1.7 扩容JDK 1.8 扩容
    扩容范围单个 Segment 独立扩容整个数组全表扩容
    并发能力单线程扩容(当前操作 Segment 的线程)多线程协同扩容(所有线程可协助迁移)
    锁影响范围仅锁定当前 Segment,其他 Segment 可用无全局锁,仅锁定迁移中的桶节点
    迁移效率单个 Segment 迁移,效率较低多线程分片迁移,效率更高
    节点迁移方式头插法(可能反转链表)尾插法(保持链表顺序,无环化风险)
    与读写的兼容性扩容时该 Segment 读写阻塞扩容与读写可并行(读无锁,写锁单个桶)

    size()区别:

    JDK 1.7 中,ConcurrentHashMap 由多个 Segment 组成,每个 Segment 独立维护自己的元素数量(count),因此计算总 size 时需要累加所有 Segment 的 count。

    • 为减少误差,JDK 1.7 采用 “重试机制”:如果两次连续累加的结果一致,则认为是准确值;若不一致(说明期间有并发修改),最多重试 3 次,若仍不一致则直接返回当前累加值(接受一定误差)。

    而在JDK 1.8中,计算 size 依赖于 baseCount 原子变量和 counterCells 辅助数组,通过无锁累加实现,返回两者的总和作为最终的 size

    当插入元素成功后,需要将总数 +1,流程如下:

    1. 优先尝试更新 baseCount
      线程通过 CAS 操作直接更新 baseCountbaseCount + 1)。

      • 若 CAS 成功:计数完成,无需其他操作。
      • 若 CAS 失败:说明存在并发竞争(多个线程同时更新 baseCount),进入下一步。
    2. 竞争激烈时,使用 counterCells 分散计数

      • 若 counterCells 未初始化,先通过 CAS 初始化(创建一个 CounterCell 数组)。
      • 线程通过哈希算法(基于线程 ID 或随机数)随机选择 counterCells 中的一个 CounterCell
      • 尝试用 CAS 更新该 CounterCell 的 valuevalue + 1):
        • 若成功:计数完成。
        • 若失败:重试或选择其他 CounterCell(避免死锁)。
    特性JDK 1.7 计数方式JDK 1.8 计数方式
    底层依赖各 Segment 的 count 累加baseCount + counterCells 累加
    并发处理重试机制减少误差,返回近似值CAS 原子操作,返回接近精确值
    性能低并发时高效,依赖 Segment 数量高低并发均高效,通过分散竞争优化
    实现复杂度简单(遍历 + 重试)复杂(原子变量 + 辅助数组 + 竞争分散)
    适用场景分段锁架构下的近似计数全局数组架构下的高效精确计数

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
    如若转载,请注明出处:http://www.pswp.cn/diannao/95631.shtml
    繁体地址,请注明出处:http://hk.pswp.cn/diannao/95631.shtml
    英文地址,请注明出处:http://en.pswp.cn/diannao/95631.shtml

    如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    nifi 增量处理组件

    在Apache NiFi中&#xff0c;QueryDatabaseTable 是一个常用的处理器&#xff0c;主要用于从关系型数据库表中增量查询数据&#xff0c;特别适合需要定期抽取新增或更新数据的场景&#xff08;如数据同步、ETL流程&#xff09;。它的核心功能是通过跟踪指定列的最大值&#xff…

    【数据可视化-90】2023 年城镇居民人均收入可视化分析:Python + pyecharts打造炫酷暗黑主题大屏

    &#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

    Multiverse模型:突破多任务处理和硬件效率瓶颈的AI创新(上)

    随着人工智能技术的快速发展&#xff0c;多模态模型成为了当前研究的热点。多模态模型的核心思想是能够同时处理和理解来自不同模态&#xff08;如文本、图像、音频等&#xff09;的数据&#xff0c;从而为模型提供更加全面的语境理解和更强的泛化能力。 杨新宇&#xff0c;卡…

    OpenCV 高斯模糊降噪

    # 高斯模糊处理(降噪) # 参数1: 原始图像 # 参数2: 高斯核尺寸(宽,高&#xff0c;必须为正奇数) # 其他模糊方法: # - cv.blur(): 均值模糊 # - cv.medianBlur(): 中值模糊 # - cv.bilateralFilter(): 双边滤波 blur cv.GaussianBlur(img, (7,7), cv…

    常见通信协议详解:TCP、UDP、HTTP/HTTPS、WebSocket 与 RPC

    在现代网络通信中&#xff0c;各种协议扮演着至关重要的角色&#xff0c;它们决定了数据如何在网络中传输、控制其可靠性、实时性与适用场景。对于开发者而言&#xff0c;理解这些常见的通信协议&#xff0c;不仅有助于更好地设计系统架构&#xff0c;还能在面对不同业务需求时…

    深入解析MPLS网络中的路由器角色

    一、 MPLS概述&#xff1a;标签交换的艺术 在深入角色之前&#xff0c;我们首先要理解MPLS的核心思想。传统IP路由是逐跳进行的&#xff0c;每一台路由器都需要对数据包的目的IP地址进行复杂的路由表查找&#xff08;最长匹配原则&#xff09;&#xff0c;这在网络核心层会造成…

    AI的拜师学艺,模型蒸馏技术

    AI的拜师学艺&#xff0c;模型蒸馏技术什么是模型蒸馏&#xff0c;模型蒸馏是一种高效的模型压缩与知识转移方法&#xff0c;通过将大型教师模型的知识精炼至小型学生模型&#xff0c;让学生模型模仿教师模型的行为和内化其知识&#xff0c;在保持模型性能的同时降低资源消耗。…

    Python爬虫从入门到精通(理论与实践)

    目录 1. 爬虫的魅力:从好奇心到数据宝藏 1.1 爬虫的基本流程 1.2 准备你的工具箱 2. 第一个爬虫:抓取网页标题和链接 2.1 代码实战:用requests和BeautifulSoup 2.2 代码解析 2.3 遇到问题怎么办? 3. 进阶爬取:结构化数据抓取 3.1 分析网页结构 3.2 代码实战:抓取…

    【DDIA】第三部分:衍生数据

    1. 章节介绍 本章节是《设计数据密集型应用》的第三部分&#xff0c;聚焦于多数据系统集成问题。前两部分探讨了分布式数据库的基础内容&#xff0c;但假设应用仅用一种数据库&#xff0c;而现实中大型应用常需组合多种数据组件。本部分旨在研究不同数据系统集成时的问题&#…

    Spring配置线程池开启异步任务

    一、单纯使用Async注解。1、Async注解在使用时&#xff0c;如果不指定线程池的名称&#xff0c;则使用Spring默认的线程池&#xff0c;Spring默认的线程池为SimpleAsyncTaskExecutor。2、方法上一旦标记了这个Async注解&#xff0c;当其它线程调用这个方法时&#xff0c;就会开…

    AI数据仓库优化数据管理

    内容概要AI数据仓库代表了现代企业数据管理的重大演进&#xff0c;它超越了传统数据仓库的范畴。其核心在于利用人工智能技术&#xff0c;特别是机器学习和深度学习算法&#xff0c;来智能化地处理从多源数据整合到最终价值提取的全过程。这种新型仓库不仅能高效地统一存储来自…

    SpringMVC(详细版从入门到精通)未完

    SpringMVC介绍 MVC模型 MVC全称Model View Controller,是一种设计创建Web应用程序的模式。这三个单词分别代表Web应用程序的三个部分: Model(模型):指数据模型。用于存储数据以及处理用户请求的业务逻辑。在Web应用中,JavaBean对象,业务模型等都属于Model。 View(视图…

    vue3运行机制同tkinter做类比

    把刚才“Vue3 盖别墅”的故事&#xff0c;和 Python 的 tkinter 做一个“一一对应”的翻译&#xff0c;你就能瞬间明白两件事的异同。 为了直观&#xff0c;用同一栋房子比喻&#xff1a; Vue3 的“网页” ⇄ tkinter 的“桌面窗口”浏览器 ⇄ Python 解释器 Tcl/Tk 引擎 下面…

    Fastadmin后台列表导出到表格

    html中添加按钮<a href"javascript:;" class"btn btn-success btn-export" title"{:__(导出数据)}" ><i class"fa fa-cloud-download"></i> {:__(导出数据)}</a>对应的js添加代码处理点击事件&#xff0c;添加…

    Nginx反向代理与缓存实现

    1. Nginx反向代理核心配置解析 1.1 反向代理基础配置结构 Nginx反向代理的基础配置结构主要包括server块和location块的配置。一个典型的反向代理配置示例如下&#xff1a; server {listen 80;server_name example.com;location / {proxy_pass http://backend_servers;proxy_se…

    第2节 如何计算神经网络的参数:AI入门核心逻辑详解

    🎯 核心目标:找到最佳w和b! 上期咱们聊了神经网络就是复杂的"线性变换+激活函数套娃",今天的重头戏就是:怎么算出让模型完美拟合数据的w(权重)和b(偏置)!先从最简单的线性函数说起,一步步揭开神秘面纱 那么如何计算w和b呢?首先明确我们需要的w和b能够让…

    AutoSar AP平台功能组并行运行原理

    在 AUTOSAR Adaptive Platform&#xff08;AP&#xff09;中&#xff0c;同一个机器上可以同时运行多个功能组&#xff08;Function Groups&#xff09;&#xff0c;即使是在单核CPU环境下。其调度机制与进程调度既相似又存在关键差异&#xff0c;具体实现如下&#xff1a;功能…

    linux服务器查看某个服务启动,运行的时间

    一 查看服务启动运行时间1.1 查看启动时间查看启动时间&#xff08;精确到秒&#xff09;&#xff1a;ps -p <PID> -o lstart例子如下&#xff1a;ps -p 1234 -o lstart1.2 查询运行时长ps -p <PID> -o etimeps -p 1234 -o etime1.3 总结

    【JS 性能】前端性能优化基石:深入理解防抖(Debounce)与节流(Throttle)

    【JS 性能】前端性能优化基石&#xff1a;深入理解防抖&#xff08;Debounce&#xff09;与节流&#xff08;Throttle&#xff09; 所属专栏&#xff1a; 《前端小技巧集合&#xff1a;让你的代码更优雅高效》 上一篇&#xff1a; 【JS 语法】代码整洁之道&#xff1a;解构赋值…

    线性代数 · 直观理解矩阵 | 空间变换 / 特征值 / 特征向量

    注&#xff1a;本文为 “线性代数 直观理解矩阵” 相关合辑。 英文引文&#xff0c;机翻未校。 如有内容异常&#xff0c;请看原文。 Understanding matrices intuitively, part 1 直观理解矩阵&#xff08;第一部分&#xff09; 333 March 201120112011 William Gould Intr…