想要搞清楚这三个概念之间的区别的话首先得弄清楚这三个概念究竟是什么
一、HashMap 的主要特性:
-
存储结构:
- 底层采用数组(哈希桶)+ 链表 / 红黑树的组合结构。当链表长度超过阈值(默认 8)时,会自动转换为红黑树,以提高查询效率。
- 通过键的哈希值确定元素在数组中的存储位置,实现快速访问。
-
键的特性:
- 键(Key)可以为
null,但只能有一个null键。 - 键是唯一的,如果插入相同的键,新值会覆盖旧值。
- 键(Key)可以为
-
无序性:
- 存储的元素顺序与插入顺序无关,遍历结果不保证有序(JDK 1.8 后在特定情况下可能保持一定顺序,但不建议依赖)。
-
线程不安全:
- HashMap 不是线程安全的,多线程环境下操作可能导致数据不一致。如需线程安全,可使用
ConcurrentHashMap或通过Collections.synchronizedMap()包装。
- HashMap 不是线程安全的,多线程环境下操作可能导致数据不一致。如需线程安全,可使用
-
性能:
- 理想情况下,插入、删除、查询操作的时间复杂度接近 O (1)。
- 性能受负载因子(默认 0.75)和初始容量影响,负载因子过大会增加哈希冲突概率,过小则浪费空间。
二、HashTable 的主要特性:
-
线程安全:
- HashTable 是线程安全的,其所有方法都被
synchronized修饰,多个线程同时操作时不会出现数据不一致问题。 - 但这也导致了性能开销较大,在单线程环境下效率通常低于 HashMap。
- HashTable 是线程安全的,其所有方法都被
-
键值的限制:
- 键(Key)和值(Value)都不能为
null,否则会抛出NullPointerException。 - 键具有唯一性,重复插入相同键会覆盖旧值。
- 键(Key)和值(Value)都不能为
-
存储结构与性能:
- 底层同样基于哈希表(数组 + 链表)实现,但不会转换为红黑树(无论链表长度如何)。
- 初始容量默认是 11,负载因子默认是 0.75,扩容时新容量为
oldCapacity * 2 + 1(与 HashMap 的扩容机制不同)。 - 查找、插入、删除的时间复杂度平均为 O (1),但因线程安全的设计,实际性能略低于 HashMap。
-
无序性:
- 与 HashMap 类似,存储的元素顺序与插入顺序无关,遍历结果不保证有序。
三、ConcurrentHashMap 的核心特性:
-
高效的线程安全机制:
- JDK 1.7:采用「分段锁(Segment)」机制,将整个哈希表分为多个 Segment,每个 Segment 独立加锁。多线程访问不同 Segment 时无需竞争锁,并发性能显著优于 Hashtable 的全局锁。
- JDK 1.8:废弃分段锁,改用「CAS + synchronized」实现。对哈希桶(数组元素)的头节点加锁,粒度更细,并发效率更高。
-
键值限制:
- 键(Key)和值(Value)都不能为
null,否则会抛出NullPointerException(与 Hashtable 一致,区别于 HashMap)。
- 键(Key)和值(Value)都不能为
-
存储结构:
- 底层与 HashMap 类似,采用「数组 + 链表 / 红黑树」结构。当链表长度超过阈值(默认 8)时,自动转换为红黑树,保证查询效率。
-
并发性能优化:
- 支持多线程同时读写,不同线程操作不同哈希桶时几乎无锁竞争。
- 读操作通常无需加锁(依赖 volatile 保证可见性),写操作仅锁定单个哈希桶,性能远高于 Hashtable。
-
迭代特性:
- 迭代过程中不会抛出
ConcurrentModificationException(fail-safe 机制),迭代器基于快照或实时数据(取决于版本),但不保证完全实时一致性。
- 迭代过程中不会抛出
四、区别
| 特性 | HashMap | Hashtable | ConcurrentHashMap |
|---|---|---|---|
| 线程安全 | 不安全 | 安全(全局锁) | 安全(细粒度锁 / CAS) |
| null 键 / 值 | 允许 null 键和值 | 不允许 null 键和值 | 不允许 null 键和值 |
| 并发性能 | 无锁,单线程高效 | 低(全局锁竞争) | 高(细粒度锁,读写分离) |
| 迭代机制 | fail-fast(结构修改抛异常) | fail-fast | fail-safe(不抛异常) |
| 适用场景 | 单线程环境 | 多线程但性能要求低 | 多线程高并发环境 |

被折叠的 条评论
为什么被折叠?



