Fork me on GitHub

HashMap与HashTable的区别

浅记一下HashMap与HashTable的区别。

作者

HashTable的作者:

1
2
3
Arthur van Hoff
Josh Bloch
Neal Gafter

HashMap的作者:

1
2
3
4
Doug Lea
Josh Bloch
Arthur van Hoff
Neal Gafter

可以看到HashMap的作者比HashTable的作者多了并发大神Doug Lea。他写了util.concurrent包。

产生时间

HashTable是Java一开始发布时就提供的键值映射的数据结构,而HashMap产生于JDK1.2。虽然HashTable比HashMap出现的早一些,但是现在HashTable基本上已经被弃用了。而HashMap已经成为应用最为广泛的一种数据类型了。造成这样的原因一方面是因为HashTable是线程安全的,效率比较低,另一方面可能是因为HashTable没有遵循驼峰命名法吧。。。

继承的父类不同

HashMap是继承字AbstractMap类,而HashTable是继承自Dictionary类。不过它们都实现了Map、Cloneable(可复制)、Serializable(可序列化)这三个接口。
而HashTable还继承了一个父类(Dictionary),这是一个已经被废弃的类。

对外提供的接口不同

HashTable比HashMap多提供了elements()和contains()两个方法。
elements()方法继承自HashTable的父类Dictionary。elements()方法用于返回此HashTable中的value的枚举。
contains()方法判断该HashTable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue()就只是调用了一下contains()方法。

对Null key 和 Null value的支持不同

HashTable既不支持Null key 也不支持 Null value。HashTable的put()方法的注释中有说明。
当key为Null时,调用put()方法,key值调用hashCode()时候会抛出空指针异常。因为拿一个Null值去调用方法了。
当value为null值时,HashTable对其做了限制,也会抛出空指针异常。
HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用contaionsKey()方法来判断。

线程安全性不同

HashTable是线程安全的,它的每个方法中都加入了Synchronize方法。在多线程并发的环境下,可以直接使用HashTable,不需要自己为它的方法实现同步。
HashMap不是线程安全的,在多线程并发的环境下,可能会产生死锁等问题。使用HashMap时就必须要自己增加同步处理,虽然HashMap不是线程安全的,但是它的效率会比Hashtable要好很多。这样设计是合理的。在我们的日常使用中,大部分时间是单线程操作的。HashMap把这部分操作解放出来了。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比HashTable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。

遍历方式的内部实现上不同

HashTable、HashMap都使用了Iterator。而由于历史原因,HashTable还使用了Enumeration的方式。
HashMap的Iterator是fail-fast迭代器。当有其它线程改变了HashMap的结构(增加、删除、修改元素),将会抛出ConcurrentModificationException。不过,通过Iterator的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。
JDK1.8之前的版本中,HashTable是没有fast-fail机制的。在JDK1.8及以后的版本中,HashTable也是使用fast-fail的。

初始容量大小和每次扩容大小的不同

HashTable默认的初始大小为11,之后每次扩容,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
但是当HashTable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。而ConcurrentHashMap引入了分割,不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。
创建时,如果给定了容量初始值,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说HashTable会尽量使用素数、奇数。而HashMap则总是使用2的幂作为哈希表的大小。
之所以会有这样的不同,是因为HashTable和HashMap设计时的侧重点不同。HashTable的侧重点是哈希的结果更加均匀,使得哈希冲突减少。当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀。而HashMap则更加关注hash的计算效率问题。在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。HashMap为了加快hash的速度,将哈希表的大小固定为了2的幂。当然这引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了一些改动。这从而导致了HashTable和HashMap的计算hash值的方法不同。
为了得到元素的位置,首先需要根据元素的key计算出一个hash值,然后再用这个hash值来计算得到最终的位置。
HashTable直接使用对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数法来获得最终的位置。
HashTable在计算元素的位置时需要进行一次除法运算,而除法运算是比较耗时的。
HashMap为了提高计算效率,将哈希表的大小固定为2的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。

Your support will encourage me to continue to create!