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

【某大厂一面】JDK1.8中对HashMap数据结构进行了哪些优化

在 JDK 1.8 中,HashMap 数据结构进行了重要的优化。相较于之前版本,JDK 1.8 引入了许多改进,提升了性能,尤其是在高负载的情况下。以下是 JDK 1.8 中 HashMap 数据结构的关键优化。

1. 链表转化为红黑树

在 JDK 1.8 之前,HashMap 使用链表来解决哈希冲突,即多个元素哈希值相同时,它们会被存储在同一个桶中,并通过链表(LinkedList)来连接。这个设计虽然简单,但当哈希冲突非常严重时,链表的长度可能变得非常长,导致查找效率下降,最坏情况下,查找时间复杂度变为 O(n),这就违背了哈希表提供常数时间查找的优势。

JDK 1.8 优化:
  • 红黑树(TreeNode)替代链表:当某个桶中的元素超过 8 个时,HashMap 会将链表转换成红黑树(TreeNode)。红黑树是一种平衡二叉查找树,可以在 O(log n) 时间复杂度内完成查找、插入和删除操作。
  • 转化条件:只有当桶中的元素超过 8 个,并且当前 HashMap 的总容量大于 64 时,才会进行链表转红黑树的操作。这是为了避免在哈希冲突不严重的情况下过度优化,影响性能。
  • 恢复链表:如果红黑树中的元素数量降到 6 以下,HashMap 会将其转换回链表,防止红黑树引入的额外复杂度影响性能。
优点:
  • 高效查找:红黑树提供了对高负载情况下的查找操作进行优化,避免了链表退化为 O(n) 的情况。
  • 动态调整:只有在真正需要的时候(即冲突非常严重时),才会使用红黑树,避免不必要的性能开销。

2. Node 类替代 Entry 类

在 JDK 1.8 中,HashMap 中的 Entry 类被 Node 类替代。这个变化本身并没有带来直接的性能提升,但它为进一步的优化和功能扩展提供了基础。

  • Node 类是一个新的内部类,它继承自 Map.Entry,表示 HashMap 中的键值对。
  • 这种修改主要是为了与 JDK 1.8 中引入的其他改进(如并发处理、扩展性等)保持一致。

3. hash 方法优化

JDK 1.8 对 hash() 方法进行了优化。在 JDK 1.7 及之前,HashMap 计算元素的哈希值时,采用了 key.hashCode() 对元素哈希值进行扰动操作,并结合原始的哈希值来减少哈希冲突。但这种方法在某些情况下可能导致哈希冲突过于集中,影响性能。

优化:
  • 扰动函数优化:JDK 1.8 对哈希值进行了更加复杂的扰动(扰动函数通过多次位运算来实现),以进一步减少冲突的概率。具体来说,hash() 方法将键的原始哈希值与其高位进行异或操作,这样可以避免低位的哈希值集中,降低了哈希冲突的概率。
static final int hash(Object key) {
    int h = key.hashCode();
    h ^= (h >>> 16);
    return h;
}

这种改进减少了哈希冲突,提高了哈希表的性能,尤其是在键的 hashCode() 分布不均的情况下。

4. resize(扩容)操作优化

HashMap 的扩容是当元素数量达到阈值时,哈希表的容量会扩大一倍(从 threshold = capacity * loadFactor 计算得出)。在 JDK 1.7 及之前,扩容过程中会将原有的所有元素重新计算哈希值并重新映射到新的数组位置。

JDK 1.8 优化:
  • 延迟扩容:在 JDK 1.8 中,当哈希表需要扩容时,resize 操作做了优化,减小了数组的复制操作。新的扩容方式引入了 分段扩容(lazy expansion),即将 resize 和元素搬迁过程异步处理,避免扩容过程中对性能产生过大的影响。
  • 更低的扩容阈值:JDK 1.8 在扩容时会减少阈值,以更快地触发扩容操作,避免哈希表过度膨胀导致性能问题。

5. 并发支持的改进

尽管 HashMap 本身并不支持多线程并发写入(如 ConcurrentHashMap),但 JDK 1.8 对 HashMap 的一些方法进行了优化,确保了在一定程度上的线程安全性:

  • computeIfAbsent:JDK 1.8 引入了 computeIfAbsent 方法,该方法可以在不需要显式同步的情况下确保元素只被计算一次。这是通过内置的 synchronized 锁来实现的,尽管它无法保证并发写入时的线程安全,但在多线程环境下读操作时能有效提高效率。

6. forEachremoveIf 方法

JDK 1.8 引入了两个方法,分别是 forEachremoveIf,它们使得 HashMap 的操作更加灵活和高效:

  • forEach:这是一个新增的默认方法,它允许我们以更声明式的方式对 HashMap 进行遍历。通过 forEach,可以减少手动创建迭代器的开销,使代码更加简洁。

    map.forEach((k, v) -> System.out.println(k + "=" + v));
    
  • removeIf:这个方法用于根据给定条件移除元素,提供了更简洁、直接的 API 接口,简化了 remove 操作。

    map.entrySet().removeIf(entry -> entry.getValue() == null);
    

7. putIfAbsentreplace 方法

这些方法在 JDK 1.8 中得到了强化,它们使得 HashMap 的写入操作更加高效且线程安全。

  • putIfAbsent:该方法用于只有在键不存在时才进行插入操作,避免了传统的先 containsKeyput 的多次判断操作,提供了原子性。
  • replace:当映射中的某个键存在时,替换对应的值。

8. 总结 JDK 1.8 的 HashMap 优化

优化点说明
链表转红黑树哈希冲突严重时,链表转为红黑树,查找、插入、删除复杂度由 O(n) 提升为 O(log n)。
hash 方法优化通过更复杂的扰动函数减少哈希冲突,提高分布均匀性。
resize 操作优化扩容时减少搬迁次数,降低性能损失。
并发支持改进增加了 computeIfAbsent 方法,简化了多线程环境下的操作。
forEachremoveIf引入新的 API,提高操作效率和代码简洁度。
putIfAbsentreplace提供原子操作方法,避免重复判断,提高并发性能。

通过这些优化,JDK 1.8 的 HashMap 在高并发、冲突频繁的情况下提供了更好的性能,减少了在常见操作中的时间复杂度,尤其是在大量数据和高负载情况下。


http://www.kler.cn/a/525621.html

相关文章:

  • 零基础Vue入门4——Vue3基础核心
  • SpringBoot源码解析(八):Bean工厂接口体系
  • 2501,20个窗口常用操作
  • 51单片机开发:串口通信
  • 16、智能驾驶域控的材料回收
  • 神经网络|(七)概率论基础知识-贝叶斯公式
  • 手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码)
  • Kafka常见问题之 org.apache.kafka.common.errors.RecordTooLargeException
  • 《DeepSeek 网页/API 性能异常(DeepSeek Web/API Degraded Performance):网络安全日志》
  • MIMIC IV数据库中mimiciv_hosp的transfers表的careunit分析
  • Java CAS操作
  • Windows平台最新视频号内容下载工具(MP4格式一键解析)
  • Vue.js 路由守卫:前置和后置守卫
  • 安卓(android)读取手机通讯录【Android移动开发基础案例教程(第2版)黑马程序员】
  • 一文大白话讲清楚webpack进阶——9——ModuleFederation实战
  • YOLO11/ultralytics:环境搭建
  • 菜鸟之路Day11-12一一集合进阶(四)
  • Effective Python:(10)
  • 电路研究9.2.5——合宙Air780EP中GPS 相关命令使用方法研究
  • 数字图像处理:实验六
  • 【RocketMQ 存储】- 一文总结 RocketMQ 的存储结构-基础
  • 基于SpringBoot的租房管理系统(含论文)
  • ICANN 关闭 WHOIS Port 43
  • SSM开发(八) MyBatis解决方法重载
  • JavaScript 注释
  • 索引01之初始索引