HashMap
、Hashtable
和 HashSet
都是 Java 中常用的集合类,它们的功能和实现有所不同,尽管它们都使用哈希表(hash table)作为底层数据结构。以下是它们之间的主要区别:
1. HashMap 和 Hashtable 的区别
特性 | HashMap | Hashtable |
---|---|---|
线程安全性 | 不支持线程安全(非同步) | 线程安全,所有方法都加了 synchronized 锁 |
性能 | 因为没有同步,性能较高(适用于单线程或同步控制由外部管理的场景) | 由于同步机制,性能相对较差 |
null 键和值 | 允许一个 null 键和多个 null 值 | 不允许 null 键和 null 值 |
迭代器 | Iterator (快速失败) | Enumerator (过时,不推荐使用) |
引入时间 | 从 JDK 1.2 引入 | 从 JDK 1.0 引入 |
扩容机制 | 默认初始容量是 16,负载因子为 0.75,扩容时容量变为原来的 2 倍 | 同样的扩容机制,但在高并发情况下性能较差 |
总结:
HashMap
是Hashtable
的改进版,不支持线程同步,因此在单线程环境中或者需要外部同步的环境中性能更高。Hashtable
是线程安全的,但性能较差,且其 API 中的Enumerator
被淘汰,现在通常推荐使用HashMap
或者更现代的并发集合类。
2. HashMap 和 HashSet 的区别
特性 | HashMap | HashSet |
---|---|---|
底层数据结构 | 基于哈希表(HashMap 是由 HashMap 实现的,键值对结构) | 基于哈希表(其实是 HashMap 的包装,元素是集合的键) |
元素类型 | 存储键值对(Key-Value),每个键映射到一个值 | 存储单一元素,不存储值,只关心键 |
键值对 | 每个元素是一个 key-value 对(即包含键和值) | 每个元素只有一个 key (没有值) |
实现接口 | 实现了 Map 接口 | 实现了 Set 接口 |
重复元素 | 允许重复值,但键不可重复 | 不允许重复元素(没有值的概念) |
添加元素的方式 | 使用 put(key, value) 添加元素 | 使用 add(element) 添加元素 |
用途 | 适用于需要存储键值对的场景 | 适用于存储唯一元素的场景(去重) |
总结:
HashMap
存储的是键值对(key-value
),是一个Map
类型的集合。HashSet
存储的是唯一的元素,它的底层是基于HashMap
实现的,实际上,它将每个元素作为HashMap
的键,并将值设置为一个常量对象。
3. Hashtable 和 HashSet 的区别
特性 | Hashtable | HashSet |
---|---|---|
底层数据结构 | 基于哈希表(Hashtable 是一个 Map 接口的实现类) | 基于哈希表(实际上是通过 HashMap 实现的,存储唯一元素) |
线程安全性 | 线程安全(方法加锁) | 不支持线程安全(非同步) |
元素类型 | 存储键值对(key-value ) | 存储单一元素,去重集合 |
重复元素 | 键不可重复,值可以重复 | 不允许重复元素(没有值的概念) |
性能 | 由于方法都加锁,性能较差 | 无锁设计,性能较高 |
用途 | 适用于存储键值对的线程安全集合 | 适用于存储不重复元素的集合(去重) |
总结:
Hashtable
是线程安全的Map
类型,存储的是键值对。HashSet
是非线程安全的集合类,存储唯一的元素(值没有意义,只有键),底层通过HashMap
实现。
小结
HashMap
:实现了Map
接口,存储键值对,线程不安全,允许null
键和null
值。Hashtable
:线程安全,存储键值对,早期版本的Map
,不允许null
键和值,性能较低。HashSet
:实现了Set
接口,存储唯一元素,底层实现使用HashMap
,线程不安全。
如果不需要线程安全,建议使用 HashMap
和 HashSet
。如果需要线程安全,可以考虑使用 ConcurrentHashMap
或者使用 Collections.synchronizedMap()
和 Collections.synchronizedSet()
来包装非线程安全的集合。