HashMap和HashTable的选择

关于编程中HashMap和HashTable的选择问题,我们可以先看看下面这些问题。

历史问题

Hashtable是个过时的集合类,是基于陈旧的Dictionary类的,并且存在于Java API中很久了。在Java 4中被重写了,实现了Map接口,所以自此以后也成了Java集合框架中的一部分,而HashMap是Java 1.2引进的Map接口的一个实现。

同步问题

HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。

迭代问题

HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。

key值问题

HashMap可以让你将空值作为一个表的条目的key或value。HashMap中只有一条记录可以是一个空的key,但任意数量的条目可以是空的value。这就是说,如果在表中没有发现搜索键,或者如果发现了搜索键,但它是一个空的值,那么get()将返回null。如果有必要,用containKey()方法来区别这两种情况。

效率问题

由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。

元素顺序

HashMap不能保证随着时间的推移Map中的元素次序是不变的。

HashMap同步

HashMap可以通过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);

术语解释

sychronized

sychronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。

fail-fast and fail-safe

关于fail-fast and fail-safe,可以参考这篇文章Fail Fast vs Fail Safe

总结

在目前的编程中基本上可以不用HashTable了,在不要求线程安全的情况下直接使用HashMap,效率比较高;如果,要求线程安全,则使用ConcurrentHashMap来代替HashMap。

参考

### 底层结构差异 在底层结构上,HashMap Hashtable 存在一定的差异。从 JDK 1.8 开始,HashMap 的底层实现采用了数组 + 链表 + 红黑树的结构。这种结构优化了在哈希冲突较多时的性能,当链表长度超过一定阈值时,链表会转换为红黑树以提高查找效率[^2]。而 Hashtable 的底层实现则是传统的数组 + 链表结构,没有引入红黑树优化机制,因此在处理大量数据高冲突场景时性能相对较差[^2]。 ### 线程安全性 HashMap 是非线程安全的,这意味着在单线程环境下,它的性能更优由于没有同步开销。然而,在多线程环境下,如果多个线程同时修改 HashMap 的结构(例如添加或删除元素),可能会导致数据不一致或其他并发问题。因此,在多线程环境中使用 HashMap 需要额外的同步措施[^5]。相反,Hashtable 是线程安全的,它的所有公共方法都被 `synchronized` 关键字修饰,确保了多线程环境下的安全性,但这同时也带来了额外的性能开销[^5]。 ### 对 null 键 null 值的支持 HashMap 允许一个 null 键多个 null 值。这种灵活性使得 HashMap 在某些应用场景下更加方便。例如,可以轻松地将 null 作为特殊值进行处理[^3]。相比之下,Hashtable 不允许任何 null 键或 null 值。尝试插入 null 键或值会导致抛出 `NullPointerException` 异常[^3]。 ### 继承体系 HashMap 继承自 `AbstractMap` 类,并实现了 `Map` 接口。这样的设计使得 HashMap 能够更好地融入现代 Java 集合框架中,提供更简洁灵活的 API[^4]。而 Hashtable 继承自已废弃的 `Dictionary` 类,虽然它也实现了 `Map` 接口,但由于其继承体系的陈旧性,使得其 API 设计不如 HashMap 现代灵活[^4]。 ### 迭代器行为 HashMap 的迭代器是 fail-fast 的,这意味着在迭代过程中,如果检测到集合被修改(除了通过迭代器自身的 `remove` 方法),则会立即抛出 `ConcurrentModificationException` 异常。这种机制有助于快速发现并发修改问题[^4]。而 Hashtable 的迭代器不是 fail-fast 的,这可能导致在并发修改时出现不可预测的行为。 ### 扩容机制 当 HashMap 的容量不足以容纳更多元素时,它会按照当前容量的两倍进行扩容。这种策略有助于保持较低的负载因子,从而减少哈希冲突的概率。同样,Hashtable 也会在容量不足时进行扩容,但其扩容策略是当前容量的两倍加一。这种差异虽然看似微小,但在特定情况下可能会影响性能。 ### 推荐使用场景 由于 HashMap 在单线程环境下的高性能表现,它通常被推荐用于不需要线程安全的场景。此外,HashMap 的子类 `LinkedHashMap` 提供了可预测的迭代顺序(默认为插入顺序),这在某些需要有序遍历的应用中非常有用[^3]。而 Hashtable 由于其线程安全特性,更适合用于多线程环境或必须保证线程安全的场景。不过,对于需要高性能并发操作的场景,推荐使用 `ConcurrentHashMap`,因为它提供了更好的并发性能[^4]。 ### 示例代码 以下是一个简单的示例代码,展示了如何在 Java 中使用 HashMap Hashtable: ```java import java.util.HashMap; import java.util.Hashtable; import java.util.Map; public class Main { public static void main(String[] args) { // 使用 HashMap Map<String, Integer> hashMap = new HashMap<>(); hashMap.put("one", 1); hashMap.put("two", 2); System.out.println("HashMap: " + hashMap); // 使用 Hashtable Map<String, Integer> hashTable = new Hashtable<>(); hashTable.put("one", 1); hashTable.put("two", 2); System.out.println("Hashtable: " + hashTable); } } ``` 这段代码演示了如何创建使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值