HashMapHashtable 的区别

1. 线程安全性

  • HashMap:是非线程安全的。这意味着在多线程环境下,如果多个线程同时对 HashMap 进行读写操作,并且至少有一个线程对 HashMap 进行了结构性的修改(例如添加或删除一个映射),则可能会导致数据不一致或抛出 ConcurrentModificationException 异常。
import java.util.HashMap;
import java.util.Map;

public class HashMapNonThreadSafeExample {
    public static void main(String[] args) {
        Map<String, Integer> hashMap = new HashMap<>();
        // 模拟多线程操作
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                hashMap.put("key" + i, i);
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                hashMap.get("key" + i);
            }
        });
        thread1.start();
        thread2.start();
    }
}
  • Hashtable:是线程安全的。它的大部分方法都使用了 synchronized 关键字进行同步,因此在多线程环境下可以安全地进行读写操作。但这种同步机制会带来一定的性能开销。
import java.util.Hashtable;
import java.util.Map;

public class HashtableThreadSafeExample {
    public static void main(String[] args) {
        Map<String, Integer> hashtable = new Hashtable<>();
        // 模拟多线程操作
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                hashtable.put("key" + i, i);
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                hashtable.get("key" + i);
            }
        });
        thread1.start();
        thread2.start();
    }
}

2. 空键和空值

  • HashMap:允许键和值为 null。但只能有一个键为 null,可以有多个值为 null
import java.util.HashMap;
import java.util.Map;

public class HashMapNullExample {
    public static void main(String[] args) {
        Map<String, Integer> hashMap = new HashMap<>();
        hashMap.put(null, 1);
        hashMap.put("key", null);
        System.out.println(hashMap);
    }
}
  • Hashtable:不允许键和值为 null。如果尝试将 null 作为键或值插入 Hashtable 中,会抛出 NullPointerException 异常。
import java.util.Hashtable;
import java.util.Map;

public class HashtableNullExample {
    public static void main(String[] args) {
        Map<String, Integer> hashtable = new Hashtable<>();
        try {
            hashtable.put(null, 1);
        } catch (NullPointerException e) {
            System.out.println("Caught NullPointerException: " + e.getMessage());
        }
    }
}

3. 继承和历史原因

  • HashMap:是 Java 1.2 引入的,继承自 AbstractMap 类。
  • Hashtable:是 Java 早期的类,继承自 Dictionary 类,从 Java 2 开始也实现了 Map 接口。

4. 性能

  • HashMap:由于是非线程安全的,没有同步开销,因此在单线程环境下性能较高。
  • Hashtable:由于使用了同步机制,在多线程环境下虽然安全,但性能相对较低。

多线程环境下的选择

在多线程环境下,一般不建议使用 Hashtable,可以根据具体需求选择以下两种替代方案:

1. ConcurrentHashMap

ConcurrentHashMap 是线程安全的,并且在多线程环境下性能比 Hashtable 高很多。它采用了分段锁(Java 7 及以前)或 CAS(Compare-And-Swap,Java 8 及以后)和 synchronized 来实现并发控制,允许多个线程同时进行读写操作,而不需要对整个 Map 进行加锁。

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
        // 模拟多线程操作
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                concurrentHashMap.put("key" + i, i);
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                concurrentHashMap.get("key" + i);
            }
        });
        thread1.start();
        thread2.start();
    }
}

2. Collections.synchronizedMap()

如果需要一个线程安全的 Map,并且对性能要求不是特别高,也可以使用 Collections.synchronizedMap() 方法将一个非线程安全的 Map(如 HashMap)转换为线程安全的 Map。但这种方式的性能通常不如 ConcurrentHashMap

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class SynchronizedMapExample {
    public static void main(String[] args) {
        Map<String, Integer> hashMap = new HashMap<>();
        Map<String, Integer> synchronizedMap = Collections.synchronizedMap(hashMap);
        // 模拟多线程操作
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                synchronizedMap.put("key" + i, i);
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                synchronizedMap.get("key" + i);
            }
        });
        thread1.start();
        thread2.start();
    }
}