HashMap和Hashtable
HashMap
和Hashtable
是 Java 中用于存储键值对的两个重要集合类,它们都基于哈希表实现,但在线程安全性、功能特性和使用场景上有显著区别。以下是详细解析:
一、核心区别总览
对比维度 | HashMap | Hashtable |
---|---|---|
线程安全性 | 非线程安全 | 线程安全(方法加synchronized ) |
键值是否允许为 null | 允许key 为null (最多一个),value 可为多个null | 不允许key 和value 为null |
继承关系 | 继承AbstractMap | 继承Dictionary (已过时) |
迭代器类型 | 快速失败迭代器(fail-fast ) | 安全失败迭代器(fail-safe ) |
性能 | 高(无同步开销) | 低(同步开销大) |
扩容机制 | 初始容量 16,扩容为原容量的 2 倍 | 初始容量 11,扩容为原容量的 2 倍 + 1 |
适用场景 | 单线程环境或并发控制由用户处理的场景 | 多线程环境(已被ConcurrentHashMap 替代) |
二、详细差异解析
1. 线程安全性
HashMap
:- 非线程安全,多个线程同时修改时可能导致数据不一致(如链表成环、元素丢失)。
- 若需线程安全,可使用
Collections.synchronizedMap(new HashMap<>())
或ConcurrentHashMap
(推荐)。
Hashtable
:- 线程安全,所有方法都被
synchronized
修饰(对整个哈希表加锁)。 - 缺点:多线程竞争时,锁竞争激烈,性能低下(同一时间只能有一个线程操作)。
- 线程安全,所有方法都被
2. 对 null 的支持
HashMap
:- 允许
key
为null
(仅允许一个,重复添加会覆盖)。 - 允许
value
为null
(可多个)。
HashMap<String, String> map = new HashMap<>(); map.put(null, "value1"); // 合法 map.put("key2", null); // 合法
- 允许
Hashtable
:- 不允许
key
或value
为null
,否则会抛出NullPointerException
。
Hashtable<String, String> table = new Hashtable<>(); table.put(null, "value"); // 抛出NullPointerException table.put("key", null); // 抛出NullPointerException
- 不允许
3. 继承关系与历史
HashMap
:- 诞生于 JDK 1.2,继承
AbstractMap
类,完全遵循Map
接口规范。
- 诞生于 JDK 1.2,继承
Hashtable
:- 诞生于 JDK 1.0,继承古老的
Dictionary
类(已被废弃),后来才实现Map
接口。 - 设计上存在历史遗留问题,如方法命名(
elements()
而非values()
)。
- 诞生于 JDK 1.0,继承古老的
4. 迭代器特性
HashMap
:- 使用快速失败迭代器(
fail-fast
):迭代过程中若修改地图结构(如添加 / 删除元素),会立即抛出ConcurrentModificationException
。 - 原理:通过
modCount
(修改次数)检测结构变化。
- 使用快速失败迭代器(
Hashtable
:- 使用安全失败迭代器(
fail-safe
):迭代器基于集合的副本工作,迭代过程中修改原集合不会抛出异常(但可能读取到旧数据)。 - 缺点:副本占用额外内存,且无法保证数据实时性。
- 使用安全失败迭代器(
5. 扩容机制
HashMap
:- 初始容量:16(必须为 2 的幂)。
- 扩容阈值:当元素数量 > 容量 × 负载因子(默认 0.75)时,扩容为原容量的 2 倍。
- 哈希计算:
(n - 1) & hash
(利用位运算高效计算索引,因容量为 2 的幂)。
Hashtable
:- 初始容量:11(非 2 的幂)。
- 扩容阈值:当元素数量 > 容量 × 负载因子(默认 0.75)时,扩容为原容量 × 2 + 1。
- 哈希计算:
hash % n
(取模运算,效率低于位运算)。
6. 性能对比
HashMap
:- 无同步锁开销,单线程环境下性能远高于
Hashtable
。 - 支持红黑树优化(JDK 8+),极端情况下查询效率更优。
- 无同步锁开销,单线程环境下性能远高于
Hashtable
:- 所有方法加锁,多线程竞争时性能差(尤其是高并发场景)。
- 无红黑树优化,长链表查询效率低。
三、代码示例对比
import java.util.HashMap;
import java.util.Hashtable;public class MapComparison {public static void main(String[] args) {// HashMap示例HashMap<String, Integer> hashMap = new HashMap<>();hashMap.put(null, 10); // 允许key为nullhashMap.put("apple", null); // 允许value为nullSystem.out.println(hashMap.get(null)); // 输出:10// Hashtable示例Hashtable<String, Integer> hashtable = new Hashtable<>();// hashtable.put(null, 20); // 抛出NullPointerException// hashtable.put("banana", null); // 抛出NullPointerExceptionhashtable.put("orange", 30);System.out.println(hashtable.get("orange")); // 输出:30}
}
四、使用场景建议
优先选择
HashMap
:- 单线程环境(如普通业务逻辑、工具类)。
- 需要存储
null
键或值的场景。 - 对性能要求较高的场景。
谨慎使用
Hashtable
:- 仅在维护旧代码时使用(历史遗留系统)。
- 多线程场景应改用
ConcurrentHashMap
(并发安全且性能更高)。
总结
HashMap
和Hashtable
的核心差异源于设计年代和线程安全策略:
HashMap
是 JDK 1.2 引入的现代实现,牺牲线程安全换取高性能,支持null
值,适合单线程场景。Hashtable
是 JDK 1.0 的古老实现,通过全表加锁保证线程安全,性能低下且不支持null
,已被ConcurrentHashMap
替代。
实际开发中,HashMap
是默认选择;多线程场景推荐ConcurrentHashMap
,而非Hashtable
。