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

【集合框架Map进阶】

👉 HashMap 在极端情况下性能会“雪崩”吗?

👉 ConcurrentHashMap 的“分段锁”和“CAS”到底是什么?

👉 WeakHashMap 真的能帮你解决内存泄漏吗?

👉 IdentityHashMapEnumMap 是什么“黑科技”?

一、HashMap 进阶:哈希冲突、扩容与性能陷阱

HashMap 是性能王者,但其背后也隐藏着需要警惕的“暗礁”。

1. 哈希冲突:从链表到红黑树的“进化”

当多个 KeyhashCode() 计算出的哈希值,经过 (n-1) & hash 计算后,落在了同一个“宝箱”(桶)时,就发生了哈希冲突

  • 链表解决冲突HashMap 通过在桶内维护一个链表来解决冲突。新元素被插入到链表头部(JDK 1.7)或尾部(JDK 1.8)。
  • 红黑树“进化”:当链表长度超过阈值(默认8),并且当前 HashMap 的数组长度 >= 64 时,链表会“进化”为红黑树
    • 目的:将最坏情况下的查找时间复杂度从 O(n) 优化到 O(log n)。
    • 条件:两个条件必须同时满足。如果数组长度 < 64,会优先选择扩容来减少冲突,而不是直接转红黑树。

// treeifyBin 方法中的关键判断
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) // MIN_TREEIFY_CAPACITY = 64resize(); // 优先扩容
elsetreeifyBin(tab, hash); // 转为红黑树

🔍 哈希函数优化:hash() 方法的“扰动” 直接使用 key.hashCode() 可能导致高位信息丢失(因为 (n-1) & hash 只用到低位)。HashMaphash() 方法通过“扰动”来减少碰撞:


static final int hash(Object key) {int h;// 将 hashCode 的高16位与低16位进行异或,让高位信息也参与运算return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

2. 扩容机制:2倍增长的“搬家”与“rehash”优化

size > thresholdcapacity * loadFactor)时,触发扩容。

  • 新容量newCap = oldCap << 1 (即 2 倍增长)。
  • rehash 过程:遍历原数组的每个桶,将其中的 Node 重新计算在新数组中的位置。
  • JDK 1.8 的“神优化”:由于数组长度是2的幂,rehash 时,一个 Node 的新位置 index 要么是 原index,要么是 原index + 原数组长度。这个判断只需要检查 hash 值的一个特定 bit 位,避免了重复的 hash 计算和取模运算,大大提升了扩容效率。

3. “哈希风暴”:性能雪崩的恐怖场景

什么是哈希风暴? 当攻击者精心构造大量 Key,使得它们的 hashCode() 都不同,但经过 (n-1) & hash 计算后,全部落在同一个桶中,形成一个超长链表。

  • 后果getputremove 操作的性能从 O(1) 退化到 O(n),HashMap 几乎瘫痪,可能导致服务拒绝(DoS)。
  • 防御
    1. 升级到 JDK 1.8+:红黑树的存在将最坏情况限制在 O(log n)。
    2. 使用随机化 hashCode:如 java.lang.String 在某些版本中引入了随机化种子。
    3. 限制输入:对不可信来源的 Key 进行校验或限制。

二、ConcurrentHashMap 进阶:从“分段锁”到“CAS”的并发艺术

ConcurrentHashMap 是高并发场景的基石,其设计思想是减少锁的粒度

1. JDK 1.7:Segment 分段锁

  • 结构:将整个 Map 逻辑上划分为多个 Segment(段),每个 Segment 继承自 ReentrantLock,是一个独立的、类似 Hashtable 的结构。
  • 锁粒度:锁的粒度从整个 Map 降低到单个 Segment。不同 Segment 上的操作可以并发进行。
  • 局限
    • Segment 数量在初始化时固定(concurrencyLevel),无法动态调整。
    • 锁粒度相比 JDK 1.8 仍然较粗。

2. JDK 1.8:CAS + synchronized 桶锁

JDK 1.8 彻底重构,回归单一的 Node<K,V>[] table,并采用更细粒度的同步策略。

  • 核心机制
    1. CAS 操作:在 table 的 Node 为 null 时,使用 Unsafe 类的 compareAndSwap 操作进行无锁插入。这是无锁化的关键。
    2. synchronized 锁桶:当桶内有元素(链表或红黑树)时,对 table[i] 这个 Node 对象加 synchronized 锁。锁的粒度降低到单个桶
    3. volatile 关键字table 数组和 Node 的 valnext 字段用 volatile 修饰,保证多线程间的可见性
  • 优势:读操作(get)完全无锁,性能极高;写操作锁粒度极小,支持更高的并发度。

3. size() 与 mappingCount():精确与近似

  • size():返回一个 int。为了保证返回值小于等于 Integer.MAX_VALUE,它需要对所有 Segment(JDK 1.7)或 CounterCell 数组(JDK 1.8)的计数进行精确求和,可能涉及一定的同步开销。
  • mappingCount():返回一个 long。在 JDK 1.8 中,它直接返回 sumCount(),这个值是近似精确的(CounterCell 的更新是 CAS 的,求和时可能有微小误差,但在绝大多数场景下足够精确),性能更高。当 Map 可能非常大时,推荐使用 mappingCount()

三、Map 的“特种兵”:WeakHashMapIdentityHashMapEnumMap

1. WeakHashMap —— 基于弱引用的“缓存管家”

WeakHashMapKey弱引用WeakReference)。

  • 核心特性:当某个 Key 对象除了在 WeakHashMap 中被弱引用外,没有其他强引用时,这个 Key 会被垃圾回收器回收,随后其对应的 Entry 也会被自动从 Map 中移除。
  • 应用场景
    • 缓存:实现“内存敏感”的缓存。当内存不足时,GC 会自动回收 Key,从而清理缓存条目。
    • 关联对象:将元数据与对象实例关联,而不会阻止对象被回收。
  • 经典误区
    • Value 不是弱引用Value 是强引用!如果 Value 很大或持有对 Key 的强引用,Entry 仍不会被清理。
    • 清理时机不确定:依赖于 GC 的运行,不是实时的。

Map<ExpensiveObject, Metadata> cache = new WeakHashMap<>();
ExpensiveObject obj = new ExpensiveObject();
cache.put(obj, new Metadata());
// 当 obj 不再被其他地方引用时,GC 会回收它,cache 中的条目也会被自动清除

2. IdentityHashMap —— 基于 == 的“身份管家”

IdentityHashMap 使用 == 运算符,而非 equals() 方法来比较 Key

  • 核心特性:只有当两个 Key 是同一个对象实例(内存地址相同)时,才认为它们相等。
  • 应用场景
    • 需要基于对象身份(identity)而非逻辑相等(equality)进行映射。
    • Key 对象的 equals() 和 hashCode() 方法被重写得不符合 Map 合约(虽然这本身就是个错误)。
    • 性能敏感场景(== 比 equals() 快)。
  • 注意:其内部使用开放寻址法(线性探测)而非链表解决冲突,行为与普通 HashMap 不同。

IdentityHashMap<String, String> map = new IdentityHashMap<>();
String s1 = new String("key");
String s2 = new String("key");
map.put(s1, "value1");
map.put(s2, "value2"); // s1 和 s2 是不同对象,所以都能放入
System.out.println(map.size()); // 输出 2

3. EnumMap —— 专为枚举优化的“极速管家”

EnumMap 是专门为 enum 类型 Key 设计的高性能 Map

  • 核心特性
    • 底层是数组EnumMap 内部使用一个 Object[] 数组。Key 的 ordinal() 值(枚举常量的序数)直接作为数组的索引。
    • 极致性能putgetremove 都是 O(1) 的数组访问,速度极快。
    • 有序:遍历顺序是 enum 常量的声明顺序。
    • Key 不能为空
  • 应用场景Key 是枚举类型时的唯一选择!性能远超 HashMap

enum Status { RUNNING, STOPPED, PAUSED }Map<Status, String> statusMsg = new EnumMap<>(Status.class);
statusMsg.put(Status.RUNNING, "系统运行中");
statusMsg.put(Status.STOPPED, "系统已停止");
// get 操作就是 array[Status.RUNNING.ordinal()],快如闪电

四、进阶高频问题 & 高分回答

Q1: HashMap 为什么在链表长度大于8时转为红黑树?为什么还要数组长度>=64?

: 转红黑树是为了将最坏情况的查找时间从 O(n) 优化到 O(log n)。阈值8是基于泊松分布计算出的概率极低的临界点。要求数组长度>=64是为了避免在数组很小时就过早地转换,因为此时扩容(rehash)是更优的减少冲突的策略。两个条件必须同时满足。

Q2: ConcurrentHashMap 在 JDK 1.8 中是如何实现高并发的?

: JDK 1.8 放弃了 Segment 分段锁,采用更细粒度的同步。核心是 CAS 操作(用于无锁插入空桶)和 synchronized 锁单个桶(锁粒度降低到数组元素)。get 操作完全无锁,volatile 保证可见性。这种设计使得读操作性能极高,写操作并发度也大幅提升。

Q3: WeakHashMap 的 Key 什么时候会被回收?Value 有影响吗?

: 当 Key 对象除了在 WeakHashMap 中被弱引用外,没有其他任何强引用时,下一次 GC 运行时,该 Key 会被回收。Value 是强引用,如果 Value 持有对 Key 的强引用,会阻止 Key 被回收,导致 Entry 无法被清理。Value 本身不会被 WeakHashMap 的机制自动清理。

Q4: EnumMap 为什么性能这么高?

EnumMap 的性能源于其极简的设计。它内部是一个数组,Key(枚举常量)的 ordinal() 值直接作为数组索引。putget 操作就是简单的数组赋值和取值,没有任何哈希计算、冲突解决或对象比较的开销,因此性能是 O(1) 且常数时间极小。

五、总结:一张表搞懂 Map 的进阶选型

场景推荐实现关键点
单线程,极致性能,无序HashMap注意哈希冲突、扩容、hashCode陷阱
单线程,需保持顺序LinkedHashMap / TreeMap前者插入/访问序,后者键有序
多线程,高并发ConcurrentHashMapJDK 1.8+ 采用 CAS + 桶锁
内存敏感的缓存WeakHashMapKey 为弱引用,依赖 GC 回收
Key 为枚举类型EnumMap基于数组,性能之王
基于对象身份映射IdentityHashMap使用 == 比较,非 equals

🔚 最后一句话 Map 的进阶,是理解哈希冲突与红黑树“进化”的智慧,是洞悉 ConcurrentHashMap 从“分段”到“无锁”的并发艺术,是掌握 WeakHashMapEnumMap 这些“特种兵”的独特用途。

http://www.dtcms.com/a/337659.html

相关文章:

  • 【VUE】Vue3 绘制 3D 蓝图利器 Grid Plan
  • 【Java】浅谈ThreadLocal
  • 【WSL2笔记10】WSL-Ubuntu 环境下 ComfyUI 本地部署性能最大化指南
  • 生产环境慎用 context.Background ():你的系统可能在 “空转”
  • CVPR 2025|英伟达联合牛津大学提出面向3D医学成像的统一分割基础模型
  • 【统刷】专题完结,题单汇总
  • 抽象工厂设计模式 Abstract Factory
  • Layui COP证书管理系统
  • html页面打水印效果
  • 码上爬第十八题【协程+webpack】
  • mongodb的高可用部署
  • ParallelWaveGAN-KaldiFree:纯Pytorch的PWG
  • 【datawhale组队学习】RAG技术 - TASK01
  • SQLsever基本操作
  • 可实时交互的AI生成世界,腾讯发布的AI框架Yan
  • keil报错:ERROR :FILE DOES NOT EXIST
  • SAP ERP移动类型 MovementType (|MM|SD|PP|FICO)
  • STL库——string(类函数学习)
  • Lucene 8.5.0 的 `.pos` 文件**逻辑结构**
  • Mybatis执行sql流程(二)之加载Mapper
  • SQL详细语法教程(六)存储+索引
  • 高效长尾关键词SEO优化
  • 汽车企业顾客满意度调查:全周期反馈解码方案(市场调研实践)
  • Redis 哨兵模式与主从架构对比
  • 江苏单电感M401A-晶晨S905L3A_频率1.5ghz赫兹_2+16G_安卓9_线刷固件包
  • iOS 应用上架全流程实践,从开发内测到正式发布的多工具组合方案
  • 决策树-信息增益(第二十三节课内容总结)
  • 上网行为安全管理与组网方案
  • 第四十天(Vue)
  • 【VUE】解决Vue路由重复导航报错