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

HashMap、HashTable、ConcurrentHashMap详解

JHashMap、HashTable、ConcurrentHashMap详解

一、概述:三者核心差异

特性维度HashMapHashTableConcurrentHashMap(JDK 1.8+)
线程安全性非线程安全线程安全(全表 synchronized 锁)线程安全(CAS + 局部 synchronized 锁)
键 / 值允许 nullKey 允许 1 个 null,Value 允许多个 nullKey/Value 均不允许 nullKey/Value 均不允许 null
底层实现(JDK1.8)数组 + 链表(阈值后转红黑树)数组 + 链表(无红黑树优化)数组 + 链表(阈值后转红黑树)
性能高(无锁竞争)低(全表锁,并发冲突严重)高(细粒度锁,支持高并发)
适用场景单线程环境、非并发场景遗留代码、低并发场景(不推荐新用)高并发场景(如分布式系统、缓存)

二、HashMap 详解

1. 核心定义与定位

HashMap 是 Java 集合框架中 最常用的非线程安全哈希表,实现了 Map 接口,基于 “数组 + 链表 / 红黑树” 的混合结构(JDK 1.8 优化),核心目标是高效查询、插入、删除(理想时间复杂度 O (1))。

// 核心继承关系
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable

2. 底层数据结构(JDK 1.8)

(1)结构组成
  • 哈希数组(table):存储键值对的核心容器,数组元素是 Node<K,V> 对象(链表节点)或 TreeNode<K,V> 对象(红黑树节点)。

  • 链表:当多个 Key 哈希冲突(哈希值对应数组索引相同)时,用链表串联节点,避免数组下标冲突。

  • 红黑树:当链表长度超过阈值(默认 8)且数组长度 ≥ 64 时,链表转为红黑树,将查询时间复杂度从 O (n) 优化为 O (logn)(避免链表过长导致性能退化)。

(2)关键参数
参数名默认值作用说明
initialCapacity16初始数组容量(必须是 2 的幂,便于通过位运算计算索引)
loadFactor0.75负载因子(衡量数组满的程度,0.75 是时间 / 空间平衡的最优值)
threshold12扩容阈值(= 容量 × 负载因子,当元素数量超过阈值时,数组扩容为原来的 2 倍)
TREEIFY_THRESHOLD8链表转红黑树的阈值
UNTREEIFY_THRESHOLD6红黑树转链表的阈值(当树节点数量减少到 6 时,转回链表节省空间)

3. 核心原理:哈希计算与索引定位

HashMap 的高效性依赖于 “通过 Key 的哈希值快速定位数组索引”,核心步骤如下:

  1. 计算 Key 的哈希值:调用 key.hashCode() 获取原始哈希值,再通过 “扰动函数” 优化哈希分布(减少冲突):
static final int hash(Object key) {int h;// 扰动函数:将哈希值的高位与低位混合,增强哈希分布均匀性return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  1. 计算数组索引:通过位运算 (n - 1) & hash 计算索引(n 是数组容量,必须是 2 的幂,确保结果在 [0, n-1] 范围内):
  • 示例:容量 n=16(二进制 10000),n-1=15(二进制 01111),与哈希值进行 & 运算,本质是 “取哈希值的低 4 位”,避免数组越界。

4. 线程安全问题

HashMap 是非线程安全的,在多线程并发操作(如同时 put、扩容)时可能出现以下问题:

  • 数据覆盖:两个线程同时计算出相同索引,后插入的元素覆盖前一个。

  • 死循环(JDK 1.7):JDK 1.7 中扩容时采用 “头插法” 移动链表节点,多线程下可能导致链表成环,查询时陷入死循环(JDK 1.8 改为 “尾插法”,已修复此问题,但仍非线程安全)。

解决方案

  • 单线程场景:直接使用 HashMap。

  • 多线程场景:使用 ConcurrentHashMap(推荐),或通过 Collections.synchronizedMap(new HashMap<>()) 包装(性能差,不推荐)。

5. 关键注意点

  • Key 的重写要求:若 Key 是自定义对象,必须同时重写 hashCode()equals() 方法,否则会导致 “相同对象判断为不同 Key” 或 “不同对象哈希冲突无法区分”。

    • 规则:equals() 返回 true 的两个对象,hashCode() 必须相等;hashCode() 相等的两个对象,equals() 不一定返回 true(哈希冲突)。
  • null 键处理:Key 允许 1 个 null(哈希值固定为 0,索引为 0),Value 允许多个 null。

三、HashTable 详解

1. 核心定义与定位

HashTable 是 Java 早期提供的线程安全哈希表,实现了 Map 接口,底层结构与 JDK 1.7 的 HashMap 类似(数组 + 链表,无红黑树优化),但因性能低下,目前已被 ConcurrentHashMap 替代,仅在遗留系统中可能见到。

// 核心继承关系
public class Hashtable<K,V> extends Dictionary<K,V>implements Map<K,V>, Cloneable, Serializable

2. 核心特性与 HashMap 的差异

(1)线程安全实现:全表 synchronized 锁

HashTable 的线程安全通过在所有核心方法(put、get、remove 等)上添加 synchronized 关键字实现,本质是 “对整个 HashTable 对象加锁”:

public synchronized V put(K key, V value) {// 禁止 key/value 为 nullif (value == null) {throw new NullPointerException();}// ... 其余逻辑
}
  • 问题:多线程并发时,无论操作哪个 Key,都会竞争同一把锁,导致锁冲突严重,并发性能极低(相当于单线程执行)。
(2)禁止 null 键 / 值

HashTable 中 Key 和 Value 均不允许为 null,否则会抛出 NullPointerException(HashMap 允许 Key 为 null)。

(3)初始容量与扩容
  • 初始容量:默认 11(HashMap 默认 16,且必须是 2 的幂)。

  • 扩容机制:当元素数量超过 容量 × 负载因子(默认 0.75) 时,扩容为 原容量 × 2 + 1(保证容量为奇数,减少哈希冲突,但计算索引时用取模 %,效率低于 HashMap 的位运算)。

3. 适用场景

  • 仅推荐用于遗留代码维护,新代码中需线程安全哈希表时,优先使用 ConcurrentHashMap

  • 低并发场景(如单线程偶尔多线程访问)也可使用,但性能不如 HashMap + 手动锁。

四、ConcurrentHashMap 详解(JDK 1.8+)

1. 核心定义与定位

ConcurrentHashMap(简称 CHM)是 Java 并发包(java.util.concurrent)提供的高性能线程安全哈希表,解决了 HashTable 全表锁的性能问题,同时保证线程安全,是高并发场景(如缓存、分布式系统)的首选。

// 核心继承关系
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>implements ConcurrentMap<K,V>, Serializable

2. 底层结构(JDK 1.8 优化)

JDK 1.8 对 CHM 进行了彻底重构,放弃了 JDK 1.7 的 “分段锁(Segment)” 机制,改为 **“数组 + 链表 / 红黑树 + CAS + 局部 synchronized 锁”** 的实现,进一步提升并发性能。

  • 核心改进:将锁粒度从 “分段” 缩小到 “数组单个节点(Node)”,即仅对哈希冲突的链表 / 红黑树的首节点加锁,不同索引的节点操作完全并行,并发度大幅提升。

3. 线程安全实现:CAS + 局部锁

CHM 的线程安全依赖两种核心机制:CAS(无锁原子操作)局部 synchronized 锁,具体逻辑如下:

(1)CAS 机制(无锁操作)

对于无哈希冲突的节点插入(即数组对应索引为 null),通过 CAS 直接原子性插入节点,无需加锁:

  • CAS 原理:Compare And Swap(比较并交换),通过硬件指令保证原子性,核心是 “先比较当前值是否符合预期,符合则修改,否则重试”。

  • 示例:插入节点时,先通过 CAS 判断数组索引位置是否为 null,若是则将节点写入,避免锁开销。

(2)局部 synchronized 锁(有冲突时加锁)

存在哈希冲突(多个 Key 对应同一索引,需操作链表 / 红黑树)时,对链表的首节点(或红黑树的根节点)加 synchronized 锁,确保同一链表 / 红黑树的操作串行执行,不同索引的操作并行执行:

// 简化逻辑:对冲突节点的首节点加锁
synchronized (f) {if (tabAt(tab, i) == f) { // 再次确认首节点未被修改(防止并发修改)// 遍历链表或红黑树,执行插入/删除操作}
}
(3)volatile 保证可见性

数组 table 被声明为 volatile,确保一个线程修改数组后,其他线程能立即看到最新值,避免 “脏读”:

private transient volatile Node<K,V>[] table;

4. 核心特性与优势

(1)高效并发
  • 锁粒度极小:仅锁定冲突的链表 / 红黑树,不同索引的操作完全并行,并发性能接近 HashMap。

  • 无锁操作:无冲突时通过 CAS 插入,避免锁开销。

(2)禁止 null 键 / 值

与 HashTable 一致,Key 和 Value 均不允许为 null,否则抛出 NullPointerException(保证并发场景下的一致性,避免 null 引发的逻辑歧义)。

(3)红黑树优化

与 HashMap 一致,当链表长度超过 8 且数组容量 ≥ 64 时,链表转为红黑树,优化查询性能(O (logn))。

(4)原子操作支持

实现 ConcurrentMap 接口,提供原子性的复合操作(无需手动加锁):

  • putIfAbsent(K key, V value):仅当 Key 不存在时插入,避免覆盖。

  • remove(Object key, Object value):仅当 Key 对应 Value 匹配时删除。

  • replace(K key, V oldValue, V newValue):仅当 Key 对应 Value 为 oldValue 时替换。

5. 与 HashTable 的性能对比

场景HashTable(全表锁)ConcurrentHashMap(局部锁)
单线程操作慢(锁开销)快(接近 HashMap)
多线程无冲突操作慢(锁竞争)快(并行执行)
多线程有冲突操作极慢(串行执行)较快(仅冲突节点串行)
高并发(100+ 线程)性能崩溃性能稳定,吞吐量高

五、三者核心区别总结与使用场景推荐

1. 核心区别对比表

对比项HashMapHashTableConcurrentHashMap(JDK1.8+)
线程安全非线程安全线程安全(全表锁)线程安全(CAS + 局部锁)
null 允许Key:1 个 null,Value: 多个 nullKey/Value: 均不允许Key/Value: 均不允许
底层结构(JDK1.8)数组 + 链表 / 红黑树数组 + 链表(无红黑树)数组 + 链表 / 红黑树
锁粒度无锁全表锁节点锁(链表首节点 / 红树根节点)
并发性能高(单线程),多线程不安全高(支持高并发)
扩容机制2 倍,初始 162 倍 + 1,初始 112 倍,初始 16

2. 使用场景推荐

  1. 单线程 / 非并发场景:优先使用 HashMap(性能最优,支持 null 键)。
  • 示例:普通业务逻辑中的本地缓存、临时数据存储。
  1. 高并发场景:必须使用 ConcurrentHashMap(性能安全兼顾,支持原子操作)。
  • 示例:分布式系统中的共享缓存、线程池任务状态存储、秒杀系统的库存计数。
  1. 遗留系统维护:仅在维护旧代码时使用 HashTable,新代码绝对避免(性能差,功能可被 ConcurrentHashMap 完全替代)。

六、常见面试题

  1. HashMap 为什么线程不安全?JDK 1.7 和 1.8 有什么区别?
  • 答:JDK 1.7 中多线程扩容可能导致链表成环(死循环),JDK 1.8 改为尾插法修复,但仍会出现数据覆盖;两者均无锁机制,多线程 put 会冲突。
  1. ConcurrentHashMap JDK 1.7 和 1.8 的实现区别是什么?
  • 答:JDK 1.7 用 “分段锁(Segment)”,将数组分为 16 个段,每段一把锁;JDK 1.8 放弃分段锁,改用 “CAS + 局部节点锁”,锁粒度更小,并发性能更高。
  1. 为什么 HashMap 的容量必须是 2 的幂?
  • 答:为了通过 (n-1) & hash 位运算计算索引(效率高于取模 %),且确保索引均匀分布在 [0, n-1] 范围内,减少哈希冲突。
  1. HashMap 的负载因子为什么默认是 0.75?
  • 答:0.75 是 “时间复杂度” 与 “空间复杂度” 的平衡:负载因子过高(如 1.0)会导致哈希冲突加剧,查询变慢;过低(如 0.5)会导致数组利用率低,浪费空间。
http://www.dtcms.com/a/447103.html

相关文章:

  • 学校 html5 网站 案例北京网站建设认
  • pve网络从Linux bridge改为ovs bridge
  • 网络课程网站模板苏州招聘网站开发
  • 2025 AI 发展双轮驱动:技术突破与产业赋能的深度实践
  • asp.net 发布网站 ftp百度推广做网站什么价位
  • linux学习笔记(14)系统调用与库函数区别及进程替换
  • 网站建设修改建议沂水县住房和建设局网站
  • 微信公众号的网站开发海口的网站建设
  • 网址制作二维码东莞网络优化公司排名
  • 网站怎么做动态图片注册个网站域名多少钱一年
  • 建e室内设计网贴图百度seo新站优化
  • 10. Pandas 分组与聚合分析(groupby)
  • 8K 剪辑大显存显卡选型实战:RTX 4090(24G)vs RTX A6000(48G)—— 从 “够用” 到 “专业” 的决策指南(一)
  • 博星卓越网站建设实验代码凡科快图官网登录入口在线
  • edge 浏览器控制台空白解决方法
  • 免费电子版个人简历可编辑网站栏目页优化
  • 有了自己的网站怎样做后台可以做一键拨号和导航的网站
  • java基础-10 : API
  • 从零开始搭建私有服务器并部署网站
  • 华为官方网站手机商城首页网站策划与建设
  • wordpress 2个菜单做中英文智推教育seo课程
  • 广东网站建设网站中国建筑集团有限公司排名
  • 覆盖9个癌种,基于11671张病理切片训练的模型登上Nature子刊,可精准“读出”分子标志物,突破传统分类局限
  • 成年做羞羞的视频网站建设网站的公司要什么资质
  • edo网站建设网站设计分析怎么写
  • 做网站要注意的广东深圳网站建设服务
  • 中心理解题3【反面提对策、文段无对策、并列关系】
  • 【无标题】大模型-扩散模型(Diffusion Model)原理讲解(3)
  • 做么自己做一个网站西安公司网站设计
  • 电子商务网站建设费用健康养生网站源码