Hashtable 是 Java 中一个非常经典的、线程安全的键值对集合,修改其数据主要有两种方式:修改值 和 修改键,这两种方式的操作和注意事项有很大不同。

核心概念回顾
在开始修改之前,我们先快速回顾一下 Hashtable 的几个关键特性:
- 线程安全:
Hashtable的几乎所有方法(如get,put,remove)都是synchronized的,这意味着它在多线程环境下可以直接使用,无需额外的同步措施,这是它与HashMap最显著的区别之一。 - 不允许 null 键和 null 值:与
HashMap不同,Hashtable的键和值都不能是null,如果尝试存入null,会抛出NullPointerException。 - 迭代器快速失败:当
Hashtable被结构化修改(即不是通过迭代器自身的remove()方法)时,任何正在进行的迭代操作都会立即抛出ConcurrentModificationException,这被称为“快速失败”机制。
修改值
这是最常见、最简单的修改方式,我们通过键来找到对应的条目,然后更新其值。
操作步骤
- 使用
get(Object key)方法获取当前值(可选,用于验证)。 - 使用
put(K key, V value)方法,如果键已存在,put方法会覆盖旧的值,并返回被覆盖的旧值,如果键不存在,则会添加一个新的键值对。
代码示例
import java.util.Hashtable;
public class HashtableModifyValueExample {
public static void main(String[] args) {
// 1. 创建一个 Hashtable 并初始化
Hashtable<String, Integer> studentScores = new Hashtable<>();
studentScores.put("Alice", 85);
studentScores.put("Bob", 92);
studentScores.put("Charlie", 78);
System.out.println("原始分数: " + studentScores);
// 2. 修改 Bob 的分数
// put 方法会返回被覆盖的旧值
Integer oldScore = studentScores.put("Bob", 95);
System.out.println("Bob 的旧分数是: " + oldScore); // 输出: 92
System.out.println("修改后的分数: " + studentScores);
// 3. 修改一个不存在的键(这实际上是添加新数据)
studentScores.put("David", 88);
System.out.println("添加新学生后的分数: " + studentScores);
}
}
输出结果:
原始分数: {Charlie=78, Bob=92, Alice=85}
Bob 的旧分数是: 92
修改后的分数: {Charlie=78, Bob=95, Alice=85}
添加新学生后的分数: {Charlie=78, Bob=95, Alice=85, David=88}
修改键
直接修改 Hashtable 的键是不允许的,因为键是 Hashtable 内部计算哈希值和存储位置的核心依据,如果你试图直接修改一个对象作为键的属性,会导致 get() 方法无法找到该键,从而产生“逻辑上的”数据丢失。

正确的“修改键”方法
正确的做法是“删除旧键,添加新键”,这是一个原子性的操作,需要分两步完成。
⚠️ 重要注意事项:
由于 Hashtable 是线程安全的,但它的方法(如 remove 和 put)是分别同步的,在多线程环境下,执行这两步操作时,其他线程可能会在中间插入修改,导致数据不一致,在多线程环境中修改键,最佳实践是使用 synchronized 块来保证操作的原子性。
代码示例
import java.util.Hashtable;
public class HashtableModifyKeyExample {
public static void main(String[] args) {
// 1. 创建一个 Hashtable
// 注意:键是一个自定义对象,我们之后要修改它的一个属性
Hashtable<Person, String> personInfo = new Hashtable<>();
personInfo.put(new Person("Alice", 30), "Engineer");
personInfo.put(new Person("Bob", 25), "Designer");
System.out.println("原始信息: " + personInfo);
// 2. 假设我们要将 "Alice" 的年龄从 30 改为 31
// 这意味着我们需要用新的键对象(年龄更新后)来替换旧的键对象
// 定义我们要修改的键
Person oldKey = new Person("Alice", 30);
String value = personInfo.get(oldKey); // 先获取对应的值
System.out.println("找到 Alice 的信息: " + value);
// --- 单线程环境下的修改 ---
// 删除旧键
personInfo.remove(oldKey);
// 创建新键
Person newKey = new Person("Alice", 31);
// 添加新键值对
personInfo.put(newKey, value);
System.out.println("修改键后的信息: " + personInfo);
// --- 多线程环境下的安全修改 ---
// 为了保证原子性,我们应该使用 synchronized
// 假设我们要再次修改 Alice 的年龄为 32
Person anotherOldKey = new Person("Alice", 31);
String anotherValue = personInfo.get(anotherOldKey);
synchronized (personInfo) { // 对整个 Hashtable 进行同步
personInfo.remove(anotherOldKey);
Person anotherNewKey = new Person("Alice", 32);
personInfo.put(anotherNewKey, anotherValue);
}
System.out.println("多线程安全修改后的信息: " + personInfo);
}
}
// 一个简单的 Person 类,重写 equals 和 hashCode 是必要的
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
return name.hashCode() + Integer.hashCode(age); // 简单的哈希计算
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
输出结果:

原始信息: {Person{name='Alice', age=30}=Engineer, Person{name='Bob', age=25}=Designer}
找到 Alice 的信息: Engineer
修改键后的信息: {Person{name='Alice', age=31}=Engineer, Person{name='Bob', age=25}=Designer}
多线程安全修改后的信息: {Person{name='Alice', age=32}=Engineer, Person{name='Bob', age=25}=Designer}
Hashtable vs. HashMap 修改操作对比
| 特性 | Hashtable |
HashMap |
|---|---|---|
| 线程安全 | 是,方法 synchronized |
否,非线程安全 |
| Null 键/值 | 不允许 | 允许 (一个 null 键,多个 null 值) |
| 修改值 | put(key, newValue) |
put(key, newValue) |
| 修改键 | 删除旧键,添加新键,需注意同步 | 删除旧键,添加新键 |
| 并发修改 | 抛出 ConcurrentModificationException |
抛出 ConcurrentModificationException |
| 性能 | 较慢,因为同步开销 | 较快 |
| 推荐场景 | 需要线程安全的简单键值存储 | 单线程或已处理同步的通用场景 |
总结与最佳实践
- 修改值:直接使用
put(K key, V value)方法即可,这是最直接的方式。 - 修改键:不要尝试直接修改键对象的内部状态,正确的做法是
remove(oldKey)put(newKey, value)。 - 多线程安全:在多线程环境下执行“删除-添加”这类复合操作时,必须使用
synchronized块来锁定Hashtable对象,以保证操作的原子性,避免竞态条件。 - 现代选择:在大多数现代 Java 应用中,如果需要线程安全的
Map,ConcurrentHashMap通常是比Hashtable更好的选择。ConcurrentHashMap提供了更高的并发性能(通过分段锁或 CAS 操作),API 更丰富(如compute,merge等原子操作),如果不需要线程安全,HashMap是首选。
希望这个详细的解释能帮助你全面理解 Java Hashtable 的修改操作!
