- 繼承類不同
HashTable繼承Dictionary類,HashMap繼承AbstractMap類
- 線程安全級別不同
HashTable是線程安全的類,每個public方法都有Synchronized修飾,HashMap不是線程安全的
- 是否允許null的要求不同
HashTable:key不允許爲null,value不允許爲null
HashMap:key允許爲null,value允許爲null
- 底層數據結構不同
在JDK1.8以後,HashMap的底層數據結構改成了數組+鏈表+紅黑樹的實現,在鏈表的節點大於TREEIFY_THRESHOLD=8時,鏈表轉爲紅黑樹,在樹節點小於UNTREEIFY_THRESHOLD=6時,紅黑樹轉變爲鏈表。(之所以有8和6兩個閾值是爲了避免某個鏈表在臨界點頻繁插入刪除,導致轉換頻繁降低性能)
而Hashtable的底層實現就是數組+鏈表,而沒有紅黑樹,因此各種操作都要簡單很多。
- 容量的要求不同
Hashtable的容量是直接使用用戶輸入的容量initialCapacity。
HashMap在用戶輸入的基礎上,強制將容量轉換爲大於輸入容量的最小2的冪次方數值,通過tableSizeFor(int cap)函數實現。
- index的計算方法不同
HashTbale是古老的除留餘數法,直接使用hashcode
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap是強制容量爲2的冪,重新根據hashcode計算hash值,在使用hash & (length-1)
,也等價取膜,但更加高效,取得的位置更加分散,偶數,奇數保證了都會分散到,HashTbale就不能保證
static int indexFor(int h, int length) {
return h & (length-1);
- 擴容方法不同
HashTable擴容後新容量是原容量的2倍+1,新threshold爲新容量*loadFactor。然後在oldMap中對數組從尾部開始遍歷,對每個鏈表從頭部開始遍歷取出節點,重新計算節點在newMap中的hashCode,放進newMap中,並把oldMap中的節點指向null。
HashMap擴容後新容量是原容量的2倍,新threshold爲2倍舊threshold。在擴容時,由於是2倍擴容,可以得到新的index要麼不變,要麼是舊index+oldCapacity。並且是從鏈表(紅黑樹)頭部開始遍歷,並將節點分別順序放到高、低兩個鏈表中,然後將鏈表頭部鏈接到數組的相應bucket中。
基於上述不同,因此hashMap的擴容效率更高,並且JDK1.8後也不會出現resize()導致的線程不安全。
- 方法不同
HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey,因爲contains方法容易讓人引起誤解。而Hashtable有contains方法、containsvalue方法和containsKey方法,其中contains方法和containsvalue方法是一樣的。
- 迭代器不同
HashMap的迭代器(Iterator)是fail-fast迭代器,所以當有其它線程改變了HashMap的結構(增加或者移除元素),將會拋出ConcurrentModificationException,但迭代器本身的remove()方法移除元素則不會拋出ConcurrentModificationException異常。
而Hashtable除了有Iterator迭代器還有enumerator迭代器,並且enumerator迭代器不是fail-fast的。
*關於fail-fast機制:fail-fast是通過modcount參數實現的,在HashMap中每當有會修改HashMap結構的操作被執行,那麼modcount加1,modcount是volatile的,因此是線程可見的,在迭代過程中,判斷modCount跟expectedModCount是否相等,如果不相等就表示已經有其他線程修改了Map。而HashTable並沒有這個參數,因此沒有fail-fast機制。