集合框架——HashTable和HashMap的區別

  • 繼承類不同

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機制。

發佈了56 篇原創文章 · 獲贊 163 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章