HashMap 和 HashTable 是 Java 中两种常用的键值对存储结构,它们都实现了 Map
接口,但在特性、性能和底层实现上有显著的区别。下面我们从多个方面详细对比它们的区别,并深入分析它们的底层实现。
1. 定义
特性 | HashMap | HashTable |
---|---|---|
定义 | 基于哈希表实现的键值对存储结构。 | 基于哈希表实现的键值对存储结构。 |
线程安全 | 非线程安全。 | 线程安全(方法使用 synchronized 修饰)。 |
继承关系 | 继承自 AbstractMap 。 | 继承自 Dictionary (已过时)。 |
2. 主要区别
特性 | HashMap | HashTable |
---|---|---|
线程安全 | 非线程安全。 | 线程安全。 |
性能 | 较高(无同步开销)。 | 较低(有同步开销)。 |
允许 null 键/值 | 允许 null 键和 null 值。 | 不允许 null 键和 null 值。 |
迭代器 | 使用 Iterator ,支持快速失败(fail-fast)。 | 使用 Enumeration ,不支持快速失败。 |
初始容量和扩容 | 默认初始容量 16,扩容时容量翻倍。 | 默认初始容量 11,扩容时容量翻倍加 1。 |
继承关系 | 继承自 AbstractMap 。 | 继承自 Dictionary (已过时)。 |
3. 示例代码
HashMap 示例:
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("A", 1);
hashMap.put("B", 2);
hashMap.put(null, 3); // 允许 null 键
hashMap.put("C", null); // 允许 null 值
System.out.println(hashMap); // 输出 {A=1, B=2, null=3, C=null}
HashTable 示例:
Map<String, Integer> hashTable = new Hashtable<>();
hashTable.put("A", 1);
hashTable.put("B", 2);
// hashTable.put(null, 3); // 报错,不允许 null 键
// hashTable.put("C", null); // 报错,不允许 null 值
System.out.println(hashTable); // 输出 {A=1, B=2}
4. 底层实现
(1) 数据结构
-
HashMap 和 HashTable 的底层都是基于哈希表实现的。
-
哈希表的核心是一个数组(
table
),数组的每个元素是一个链表或红黑树(Java 8 之后,当链表长度超过阈值时会转换为红黑树)。
(2) 哈希冲突
-
当两个键的哈希值相同时,会发生哈希冲突。
-
冲突的键值对会存储在同一个桶(Bucket)中,形成链表或红黑树。
(3) 扩容机制
-
当元素数量超过容量与负载因子的乘积时,会触发扩容。
-
HashMap 和 HashTable 的扩容机制类似,但初始容量和扩容策略略有不同:
-
HashMap:默认初始容量 16,扩容时容量翻倍。
-
HashTable:默认初始容量 11,扩容时容量翻倍加 1。
-
5. 详细对比
(1) 线程安全性
-
HashMap:
-
非线程安全。
-
如果需要线程安全,可以使用
Collections.synchronizedMap(new HashMap<>())
或ConcurrentHashMap
。
-
-
HashTable:
-
线程安全,所有方法都用
synchronized
修饰。 -
由于同步开销,性能较低。
-
(2) 允许 null 键/值
-
HashMap:
-
允许
null
键和null
值。
-
hashMap.put(null, 1); // 允许
hashMap.put("A", null); // 允许
HashTable:
-
不允许
null
键和null
值。
hashTable.put(null, 1); // 报错
hashTable.put("A", null); // 报错
(3) 迭代器
-
HashMap:
-
使用
Iterator
,支持快速失败(fail-fast)。 -
如果在迭代过程中修改了集合(除了通过
Iterator
的remove
方法),会抛出ConcurrentModificationException
。
-
-
HashTable:
-
使用
Enumeration
,不支持快速失败。 -
如果在迭代过程中修改了集合,不会抛出异常。
-
(4) 性能
-
HashMap:
-
由于没有同步开销,性能较高。
-
-
HashTable:
-
由于所有方法都用
synchronized
修饰,性能较低。
-
6. 使用场景
场景 | HashMap | HashTable |
---|---|---|
单线程环境 | 适合。 | 不适合(性能较低)。 |
多线程环境 | 不适合(需额外同步)。 | 适合(线程安全)。 |
需要允许 null 键/值 | 适合。 | 不适合。 |
需要快速失败机制 | 适合。 | 不适合。 |
7. 总结
特性 | HashMap | HashTable |
---|---|---|
线程安全 | 非线程安全。 | 线程安全。 |
性能 | 较高。 | 较低。 |
允许 null 键/值 | 允许。 | 不允许。 |
迭代器 | 支持快速失败。 | 不支持快速失败。 |
初始容量和扩容 | 默认 16,扩容翻倍。 | 默认 11,扩容翻倍加 1。 |
继承关系 | 继承自 AbstractMap 。 | 继承自 Dictionary (已过时)。 |
8. 一句话总结
-
HashMap:非线程安全,性能高,允许
null
键/值,适合单线程环境。 -
HashTable:线程安全,性能低,不允许
null
键/值,适合多线程环境。