贝博恩创新科技网

Java Hashtable教程,如何快速掌握核心用法?

Java Hashtable 完整教程

Hashtable 是 Java 集合框架中一个非常经典的类,它实现了 Map 接口,用于存储键值对,它的核心特点是线程安全不允许键或值为 null

Java Hashtable教程,如何快速掌握核心用法?-图1
(图片来源网络,侵删)

目录

  1. Hashtable 是什么?
  2. 为什么需要 Hashtable?(线程安全)
  3. Hashtable 的核心特性
  4. 如何使用 Hashtable?(基本操作)
    • 创建 Hashtable
    • 添加元素 (put)
    • 获取元素 (get)
    • 删除元素 (remove)
    • 判断是否存在 (containsKey, containsValue)
    • 遍历 Hashtable
    • 获取大小 (size)
  5. HashtableHashMap 的核心区别
  6. 何时使用 Hashtable
  7. 现代替代方案:ConcurrentHashMap
  8. 完整代码示例

Hashtable 是什么?

Hashtable(哈希表)是基于哈希表数据结构的 Map 实现,它通过一个“键”来快速查找、插入和删除对应的“值”,内部,它使用一个数组来存储数据,并通过键的哈希码计算出在数组中的存储位置,从而实现高效的存取操作。

在 Java 中,Hashtable 继承自 Dictionary 类(一个已过时的类),并实现了 Map 接口。

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {
    // ... 内部实现
}

为什么需要 Hashtable?(线程安全)

Hashtable 最主要的特点是它是线程安全的,这意味着在多线程环境下,当多个线程同时读写同一个 Hashtable 实例时,不需要额外的同步措施(如 synchronized 关键字或 Lock 对象),Hashtable 内部的方法都是同步的,可以保证数据的一致性。

在 Java 的早期版本(JDK 1.2 之前)中,Hashtable 是主要的 Map 实现,并且是为多线程环境设计的。

Java Hashtable教程,如何快速掌握核心用法?-图2
(图片来源网络,侵删)

Hashtable 的核心特性

  1. 线程安全:所有公共方法(如 get, put, remove 等)都使用 synchronized 关键字进行同步,确保在多线程环境下的安全访问。
  2. 不允许 null 键和 null:与 HashMap 不同,如果你尝试向 Hashtable 中插入一个 null 键或 null 值,它会抛出 NullPointerException
  3. 迭代器是 fail-fast 的:当 Hashtable 被创建后,如果迭代器正在遍历,而另一个线程修改了 Hashtable 的结构(非迭代器自身的 remove 方法),迭代器会立即抛出 ConcurrentModificationException
  4. 不保证有序性:在 Java 1.8 之前,Hashtable 的迭代顺序是不确定的,在 Java 1.8 及之后,虽然底层实现有所优化,但它仍然不保证迭代的顺序(如插入顺序或访问顺序)。

如何使用 Hashtable?(基本操作)

创建 Hashtable

Hashtable 有几个构造函数,最常用的是指定初始容量和加载因子。

// 1. 创建一个默认的 Hashtable,初始容量为 11,加载因子为 0.75
Hashtable<String, Integer> scores = new Hashtable<>();
// 2. 创建一个指定初始容量的 Hashtable
Hashtable<String, Integer> scoresWithCapacity = new Hashtable<>(20);
// 3. 创建一个指定初始容量和加载因子的 Hashtable
// 加载因子是 0.8,意味着当元素数量达到 容量 * 0.8 时,哈希表会进行扩容
Hashtable<String, Integer> scoresWithLoadFactor = new Hashtable<>(20, 0.8f);

添加元素 (put)

使用 put(K key, V value) 方法添加键值对,如果键已存在,则旧的值会被新的值覆盖。

scores.put("Alice", 95);
scores.put("Bob", 88);
scores.put("Charlie", 76);
// 尝试添加 null 键或值会抛出 NullPointerException
// scores.put(null, 100); // 抛出 NullPointerException
// scores.put("David", null); // 抛出 NullPointerException

获取元素 (get)

使用 get(Object key) 方法根据键获取对应的值,如果键不存在,则返回 null

Integer aliceScore = scores.get("Alice"); // 返回 95
Integer davidScore = scores.get("David"); // 返回 null

删除元素 (remove)

使用 remove(Object key) 方法删除指定的键值对,并返回被删除的值,如果键不存在,则返回 null

Java Hashtable教程,如何快速掌握核心用法?-图3
(图片来源网络,侵删)
Integer removedScore = scores.remove("Bob"); // 返回 88,并从表中移除 "Bob"

判断是否存在 (containsKey, containsValue)

  • containsKey(Object key):检查 Hashtable 中是否包含指定的键。
  • containsValue(Object value):检查 Hashtable 中是否包含指定的值。
boolean hasAlice = scores.containsKey("Alice"); // 返回 true
boolean hasScore88 = scores.containsValue(88);  // 返回 false (因为 Bob 已被删除)

遍历 Hashtable

有几种方式可以遍历 Hashtable

使用 keySet()for-each 循环(推荐)

System.out.println("遍历所有键:");
for (String name : scores.keySet()) {
    System.out.println("Name: " + name + ", Score: " + scores.get(name));
}

使用 entrySet()for-each 循环(最高效)

System.out.println("遍历所有键值对:");
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
    System.out.println("Name: " + entry.getKey() + ", Score: " + entry.getValue());
}

使用迭代器

System.out.println("使用迭代器遍历:");
Iterator<String> iterator = scores.keySet().iterator();
while (iterator.hasNext()) {
    String name = iterator.next();
    System.out.println("Name: " + name + ", Score: " + scores.get(name));
}

获取大小 (size)

使用 size() 方法获取 Hashtable 中键值对的数量。

int size = scores.size(); // 返回 2 (Alice 和 Charlie)

HashtableHashMap 的核心区别

这是一个面试中非常常见的问题,以下是两者最主要的区别:

特性 Hashtable HashMap
线程安全 线程安全,方法使用 synchronized 非线程安全
Null 键和值 不允许 null 键和 null 允许一个 null 键和多个 null
性能 较慢,因为每个方法都有同步开销 更快,没有同步开销
父类 继承自过时的 Dictionary 实现了 Map 接口
迭代器 迭代器是 fail-fast 的 迭代器也是 fail-fast 的
遍历方式 使用 Enumeration 或迭代器 使用迭代器或 for-each 循环
推荐使用 在多线程环境中(但通常有更好的选择) 在单线程环境中是首选

何时使用 Hashtable

由于 Hashtable 的同步开销较大,在现代 Java 开发中,直接使用 Hashtable 的场景已经非常少

  • 不推荐使用 Hashtable 的情况:在单线程应用中,应优先使用性能更高的 HashMap,在多线程应用中,Hashtable 也是一个可行的选择,但通常有更优的替代方案。

现代替代方案:ConcurrentHashMap

从 Java 5 开始,java.util.concurrent 包提供了 ConcurrentHashMap 类,它是 Hashtable 的一个现代、高性能替代品。

为什么 ConcurrentHashMap 更好?

  • 更细粒度的锁Hashtable 是在方法级别上锁的,意味着任何操作都会锁定整个表,而 ConcurrentHashMap 使用分段锁(在 Java 8 中改为 CAS + synchronized),它只锁定需要修改的数据段,而不是整个表,这大大提高了并发性能。
  • 更好的可伸缩性:在高并发场景下,ConcurrentHashMap 的吞吐量远高于 Hashtable

如果你需要一个线程安全的 MapConcurrentHashMap 几乎总是比 Hashtable 更好的选择

完整代码示例

下面是一个综合示例,展示了 Hashtable 的基本用法和线程安全特性。

import java.util.Hashtable;
import java.util.Map;
public class HashtableExample {
    public static void main(String[] args) {
        // 1. 创建并初始化 Hashtable
        Hashtable<String, String> capitalCities = new Hashtable<>();
        capitalCities.put("USA", "Washington, D.C.");
        capitalCities.put("UK", "London");
        capitalCities.put("Germany", "Berlin");
        capitalCities.put("France", "Paris");
        System.out.println("初始的 Hashtable: " + capitalCities);
        // 2. 获取元素
        String capitalOfUSA = capitalCities.get("USA");
        System.out.println("美国的首都是: " + capitalOfUSA);
        // 3. 检查是否存在
        boolean hasCanada = capitalCities.containsKey("Canada");
        System.out.println("包含 'Canada' 吗? " + hasCanada);
        // 4. 删除元素
        capitalCities.remove("France");
        System.out.println("删除 'France' 后: " + capitalCities);
        // 5. 遍历 Hashtable
        System.out.println("\n遍历所有国家及其首都:");
        for (Map.Entry<String, String> entry : capitalCities.entrySet()) {
            System.out.println("国家: " + entry.getKey() + ", 首都: " + entry.getValue());
        }
        // 6. 尝试添加 null 值(会抛出异常)
        try {
            capitalCities.put("Italy", null);
        } catch (NullPointerException e) {
            System.out.println("\n捕获到异常: " + e.getMessage());
        }
        // 7. 线程安全演示 (简单模拟)
        // 注意:这个简单示例并不能完全展示线程安全,但我们可以相信 Hashtable 的设计
        Runnable task = () -> {
            for (int i = 0; i < 100; i++) {
                capitalCities.put("Thread-" + Thread.currentThread().getId() + "-" + i, "Value-" + i);
            }
        };
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("\n多线程操作后的 Hashtable 大小: " + capitalCities.size());
    }
}
  • Hashtable 是一个线程安全Map 实现,适用于多线程环境。
  • 它的主要缺点是性能较低(因为方法级别的同步)和不允许 null 键/值
  • 在现代 Java 编程中,对于单线程环境,应使用 HashMap
  • 对于多线程环境,应优先考虑使用 ConcurrentHashMap,因为它提供了更好的并发性能和可伸缩性。
  • Hashtable 是 Java 历史发展的一部分,了解它有助于理解 Java 集合框架的演进,但在新项目中应谨慎选择。
分享:
扫描分享到社交APP
上一篇
下一篇