HashMap 允许存储一个 null 键和多个 null 值,而 Hashtable 对键和值都禁止为 null,尝试存入会直接抛出异常。在现代 Java 开发中,除非维护遗留系统,否则建议优先使用 HashMap,多线程场景则考虑 ConcurrentHashMap。
核心结论:HashMap 设计更灵活支持 null,Hashtable 因早期线程安全设计禁止 null,现代开发应避免使用 Hashtable。
- 适用场景:单线程首选 HashMap,多线程首选 ConcurrentHashMap,尽量避免 Hashtable。
- 关键区别:HashMap 允许 1 个 null 键和多个 null 值,Hashtable 键值全禁止,存入即抛 NullPointerException。
- 注意事项:HashMap 获取 null 值时需区分是键不存在还是值本身为 null,Hashtable 无此困扰但灵活性差。
代码验证与行为差异
通过以下代码可直接观察两者对 null 的处理差异。Hashtable 在 put 阶段就会检查 null,而 HashMap 允许存入。
import java.util.*;
public class NullKeyTest {
public static void main(String[] args) {
// HashMap 允许 null
Map<String, String> hashMap = new HashMap<>();
hashMap.put(null, "value"); // 成功
hashMap.put("key", null); // 成功
System.out.println("HashMap put null: Success");
// Hashtable 禁止 null
Map<String, String> hashtable = new Hashtable<>();
try {
hashtable.put(null, "value"); // 抛出 NullPointerException
} catch (NullPointerException e) {
System.out.println("Hashtable put null key: Failed - " + e.getClass().getSimpleName());
}
try {
hashtable.put("key", null); // 抛出 NullPointerException
} catch (NullPointerException e) {
System.out.println("Hashtable put null value: Failed - " + e.getClass().getSimpleName());
}
}
}底层原理与异常触发点
两者底层处理机制不同。HashMap 在计算哈希值时,对 null 键做了特殊处理,将其哈希值固定为 0,存储在数组索引 0 的位置,因此不会调用 null 对象的 hashCode 方法。
Hashtable 的 put 方法内部直接进行了 null 检查。源码中大致逻辑为:
// Hashtable.put 简化逻辑
public synchronized V put(K key, V value) {
if (value == null) { // 显式检查 value
throw new NullPointerException();
}
// ... 后续操作也会检查 key
if (key == null) {
throw new NullPointerException();
}
// ...
}此外,Hashtable 是 JDK 1.0 的遗留类,设计之初为了线程安全采用了同步方法,当时认为 null 语义模糊(无法区分键不存在还是值为 null),因此禁止 null。HashMap 是后续集合框架的一部分,设计更灵活,允许 null 以满足特定业务场景。
性能与线程安全场景
除了 null 处理,两者在并发场景下的表现差异巨大:
- Hashtable:所有公共方法都使用
synchronized修饰,属于独占锁。在高并发下,所有线程竞争同一把锁,性能瓶颈明显。 - HashMap:非线程安全,单线程性能最优。多线程环境下直接使用会导致数据覆盖或死循环(JDK1.7)。
- ConcurrentHashMap:现代并发首选。JDK1.8 采用 CAS + synchronized 锁桶节点,粒度更细,性能远高于 Hashtable。
建议:如果是多线程环境,不要为了 null 支持强行用 HashMap 加锁,建议直接使用 ConcurrentHashMap(注意 ConcurrentHashMap 通常也不允许 null 键值)。
开发避坑指南
1. get 返回 null 的歧义:在 HashMap 中,get(key) 返回 null 可能是值本身为 null,也可能是键不存在。需配合 containsKey 使用。
if (map.containsKey(key)) {
// 键存在,值可能为 null
} else {
// 键不存在
}2. 重复 put null 键:HashMap 只允许一个 null 键,多次 put(null, value) 会覆盖旧值,不会报错但可能逻辑不符。
3. 遗留代码重构:如果发现旧代码使用 Hashtable,且无强线程安全依赖,可重构为 HashMap 或 ConcurrentHashMap 以提升性能。