Java中HashMap和Hashtable的区别

Java中HashMap和Hashtable的区别

技术背景

在Java编程中,HashMapHashtable 都是用于存储键值对的数据结构,它们都利用哈希技术来存储唯一的键。Hashtable 是JDK 1.0中引入的遗留类,而 HashMap 是JDK 1.2中作为Java集合框架的一部分引入的。随着Java的发展,了解它们之间的区别对于选择合适的数据结构至关重要。

实现步骤

1. 同步性

  • Hashtable:是同步的,这意味着同一时间只有一个线程可以修改哈希表。任何线程在对 Hashtable 进行更新操作之前,都必须获取该对象的锁,其他线程则需要等待锁被释放。这使得 Hashtable 适合在多线程环境中使用,但会带来一定的性能开销。
  • HashMap:是非同步的,不是线程安全的,因此在多线程环境中使用时,如果没有适当的同步代码,不能在多个线程之间共享。不过,在单线程环境中,由于不需要进行同步操作,HashMap 的性能通常更好。

2. 对null的支持

  • Hashtable:不允许使用null作为键或值。如果尝试插入null键或值,会抛出 NullPointerException
  • HashMap:允许使用一个null键和任意数量的null值。

3. 迭代器和枚举器

  • Hashtable:可以通过枚举器(Enumeration)和迭代器(Iterator)进行遍历。枚举器是fail-safe的,即当集合在迭代过程中被修改时,不会抛出异常;而迭代器是fail-fast的,如果在迭代过程中集合被结构修改(除了迭代器自身的 remove() 方法),会抛出 ConcurrentModificationException
  • HashMap:只能通过迭代器进行遍历,并且迭代器是fail-fast的。

4. 继承关系

  • Hashtable:继承自 Dictionary 类,该类现在已被视为过时,从JDK 1.2开始,Hashtable 被重新设计以实现 Map 接口,成为Java集合框架的一员。
  • HashMap:继承自 AbstractMap 类,并且从一开始就是Java集合框架的成员。

5. 初始容量和负载因子

  • Hashtable:默认初始容量为11,负载因子为0.75。
  • HashMap:默认初始容量为16,负载因子同样为0.75。

6. 结构修改处理(Java 8及以后)

  • Hashtable:在发生哈希冲突时,会将映射条目存储在链表中。
  • HashMap:在Java 8中,如果哈希桶中的节点数量超过8个,链表会转换为红黑树;当桶中的节点数量减少到6个以下时,红黑树会转换回链表。这一改进提高了在高哈希冲突情况下的查找性能,最坏情况下的时间复杂度从O(n)降低到O(log n)。

核心代码

Hashtable示例

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.Hashtable;
import java.util.Map;

public class HashtableExample {
public static void main(String[] args) {
Map<Integer, String> states = new Hashtable<>();
states.put(1, "INDIA");
states.put(2, "USA");
// states.put(3, null); // 会抛出NullPointerException
System.out.println(states.get(1));
System.out.println(states.get(2));
}
}

HashMap示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.HashMap;
import java.util.Map;

public class HashMapExample {
public static void main(String[] args) {
Map<Integer, String> states = new HashMap<>();
states.put(1, "INDIA");
states.put(2, "USA");
states.put(3, null);
states.put(null, "UK");
System.out.println(states.get(1));
System.out.println(states.get(2));
System.out.println(states.get(3));
System.out.println(states.get(null));
}
}

最佳实践

  • 单线程环境:如果应用程序是单线程的,建议使用 HashMap,因为它的性能更好,并且允许使用null键和值。
  • 多线程环境
    • 如果需要完全的线程安全,可以使用 Hashtable,但要注意其性能开销。
    • 更好的选择是使用 ConcurrentHashMap,它是Java 5引入的,提供了比 Hashtable 更好的可扩展性。
    • 如果只需要在某些操作上进行同步,可以使用 Collections.synchronizedMap() 方法将 HashMap 转换为同步的映射。
1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class SynchronizedHashMapExample {
public static void main(String[] args) {
Map<Integer, String> hashMap = new HashMap<>();
Map<Integer, String> synchronizedMap = Collections.synchronizedMap(hashMap);
synchronized (synchronizedMap) {
synchronizedMap.put(1, "Value");
}
}
}

常见问题

1. 为什么 Hashtable 被认为是遗留类?

Hashtable 是JDK 1.0中引入的,随着Java集合框架的发展,它的设计和功能已经有了更好的替代方案。例如,ConcurrentHashMap 提供了更好的并发性能,而 HashMap 在单线程环境中性能更优。此外,Hashtable 继承自过时的 Dictionary 类,其API也不如现代的集合类简洁和灵活。

2. HashMap 的迭代器为什么是fail-fast的?

HashMap 的迭代器设计为fail-fast是为了快速检测到并发修改,避免在迭代过程中出现数据不一致的问题。当检测到并发修改时,迭代器会立即抛出 ConcurrentModificationException,提醒开发者需要处理并发问题。

3. ConcurrentHashMapHashtable 有什么区别?

  • 同步粒度Hashtable 对整个哈希表进行同步,同一时间只有一个线程可以访问;而 ConcurrentHashMap 采用分段锁或CAS(Compare-And-Swap)机制,允许多个线程同时访问不同的段,提高了并发性能。
  • 读操作Hashtable 的读操作也是同步的,而 ConcurrentHashMap 的读操作通常不需要加锁,因此在高并发读的场景下性能更好。
  • 对null的支持ConcurrentHashMap 不允许使用null键或值,与 Hashtable 相同。

Java中HashMap和Hashtable的区别
https://119291.xyz/posts/2025-04-18.differences-between-hashmap-and-hashtable-in-java/
作者
ww
发布于
2025年4月18日
许可协议