HashMap和Hashtable的區別
一、HashMap簡介
HashMap是在JDK1.2中引入的Map的實現類。
1.HashMap是基於哈希表實現的,每一個元素是一個key-value對,其內部通過單鏈表解決沖突問題,容量不足(超過了閾值)時,同樣會自動增長。
2. HashMap是非線程安全的,只是用於單線程環境下,多線程環境下可以采用concurrent並發包下的concurrentHashMap。 3.HashMap 實現了Serializable接口,因此它支持序列化,實現了Cloneable接口,能被克隆。 4.HashMap存數據的過程是: HashMap內部維護了一個存儲數據的Entry數組,HashMap采用鏈表解決沖突,每一個Entry本質上是一個單向鏈表。當准備添加一個key-value對時
首先通過hash(key)方法計算hash值,然后通過indexFor(hash,length)求該key-value對的存儲位置,計算方法是先用hash&0x7FFFFFFF后,
再對length取模,這就保證每一個key-value對都能存入HashMap中,當計算出的位置相同時,由於存入位置是一個鏈表,則把這個key-value對插入鏈表頭。 5.HashMap中key和value都允許為null。key為null的鍵值對永遠都放在以table[0]為頭結點的鏈表中。
圖中,紫色部分即代表哈希表,也稱為哈希數組,數組的每個元素都是一個單鏈表的頭節點,鏈表是用來解決沖突的,如果不同的key映射到了數組的同一位置處,就將其放入單鏈表中。
HashMap內存儲數據的Entry數組默認是16,如果沒有對Entry擴容機制的話,當存儲的數據一多,Entry內部的鏈表會很長,這就失去了HashMap的存儲意義了。所以HasnMap內部有自己的擴容機制。
HashMap內部有: 變量size,它記錄HashMap的底層數組中已用槽的數量; 變量threshold,它是HashMap的閾值,用於判斷是否需要調整HashMap的容量(threshold = 容量*加載因子) 變量DEFAULT_LOAD_FACTOR = 0.75f,默認加載因子為0.75 HashMap擴容的條件是:
當size大於threshold時,對HashMap進行擴容 擴容是是新建了一個HashMap的底層數組,而后調用transfer方法,將就HashMap的全部元素添加到新的HashMap中(要重新計算元素在新的數組中的索引位置)。
很明顯,擴容是一個相當耗時的操作,因為它需要重新計算這些元素在新的數組中的位置並進行復制處理。因此,我們在用HashMap的時,最好能提前預估下HashMap中元素的個數,這樣有助於提高HashMap的性能。 HashMap共有四個構造方法。
構造方法中提到了兩個很重要的參數:初始容量和加載因子。這兩個參數是影響HashMap性能的重要參數。
其中容量表示哈希表中槽的數量(即哈希數組的長度),初始容量是創建哈希表時的容量(從構造函數中可以看出,如果不指明,則默認為16)
加載因子是哈希表在其容量自動增加之前可以達到多滿的一種尺度,當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行 resize 操作(即擴容)。 下面說下加載因子,如果加載因子越大,對空間的利用更充分,但是查找效率會降低(鏈表長度會越來越長);
如果加載因子太小,那么表中的數據將過於稀疏(很多空間還沒用,就開始擴容了),對空間造成嚴重浪費。如果我們在構造方法中不指定,則系統默認加載因子為0.75,這是一個比較理想的值,一般情況下我們是無需修改的。 另外,無論我們指定的容量為多少,構造方法都會將實際容量設為不小於指定容量的2的次方的一個數,且最大值不能超過2的30次方。
二、Hashtable簡介
Hashtable同樣是基於哈希表實現的,同樣每個元素是一個key-value對,其內部也是通過單鏈表解決沖突問題,容量不足(超過了閾值)時,同樣會自動增長。 Hashtable也是JDK1.0引入的類,是線程安全的,能用於多線程環境中。 Hashtable同樣實現了Serializable接口,它支持序列化,實現了Cloneable接口,能被克隆。
三.分析二者不同
1、繼承的父類不同
HashMap繼承自AbstractMap類。但二者都實現了Map接口。
Hashtable繼承自Dictionary類,Dictionary類是一個已經被廢棄的類(見其源碼中的注釋)。父類都被廢棄,自然而然也沒人用它的子類Hashtable了。
2、HashMap線程不安全,HashTable線程安全
javadoc中關於hashmap的一段描述如下:此實現不是同步的。如果多個線程同時訪問一個哈希映射,而其中至少一個線程從結構上修改了該映射,則它必須保持外部同步。
Hashtable 中的方法大多是Synchronize的,而HashMap中的方法在一般情況下是非Synchronize的。
在多線程並發的環境下,可以直接使用Hashtable,不需要自己為它的方法實現同步,但使用HashMap時就必須要自己增加同步處理。
HashTable實現線程安全的代價就是效率變低,因為會鎖住整個HashTable,而ConcurrentHashMap做了相關優化,因為ConcurrentHashMap使用了分段鎖,並不對整個數據進行鎖定,效率比HashTable高很多。
HashMap底層是一個Entry數組,當發生hash沖突的時候,hashmap是采用鏈表的方式來解決的,在對應的數組位置存放鏈表的頭結點。對鏈表而言,新加入的節點會從頭結點加入。
3.包含的contains方法不同
HashMap是沒有contains方法的,而包括containsValue和containsKey方法;hashtable則保留了contains方法,效果同containsValue,還包括containsValue和containsKey方法。
4.是否允許null值
Hashmap是允許key和value為null值的,用containsValue和containsKey方法判斷是否包含對應鍵值對;HashTable鍵值對都不能為空,否則包空指針異常。
5.計算hash值方式不同
為了得到元素的位置,首先需要根據元素的 KEY計算出一個hash值,然后再用這個hash值來計算得到最終的位置。
①:HashMap有個hash方法重新計算了key的hash值,因為hash沖突變高,所以通過一種方法重算hash值的方法:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
注意這里計算hash值,先調用hashCode方法計算出來一個hash值,再將hash與右移16位后相異或,從而得到新的hash值。
②:Hashtable通過計算key的hashCode()來得到hash值就為最終hash值。
它們計算索引位置方法不同:
HashMap在求hash值對應的位置索引時,index = (n - 1) & hash。將哈希表的大小固定為了2的冪,因為是取模得到索引值,故這樣取模時,不需要做除法,只需要做位運算。位運算比除法的效率要高很多。
HashTable在求hash值位置索引時計算index的方法:
int index = (hash & 0x7FFFFFFF) % tab.length;
&0x7FFFFFFF的目的是為了將負的hash值轉化為正值,因為hash值有可能為負數,而&0x7FFFFFFF后,只有符號位改變,而后面的位都不變。
6.擴容方式不同(容量不夠)
當容量不足時要進行resize方法,而resize的兩個步驟:
①擴容;
②rehash:這里HashMap和HashTable都會會重新計算hash值而這里的計算方式就不同了(看5);
HashMap 哈希擴容必須要求為原容量的2倍,而且一定是2的冪次倍擴容結果,而且每次擴容時,原來數組中的元素依次重新計算存放位置,並重新插入;
而Hashtable擴容為原容量2倍加1;
7.解決hash沖突方式不同(地址沖突)
先看jdk8之前:
查找時間復雜度慢慢變高;
Java8,HashMap中,當出現沖突時可以:
1.如果沖突數量小於8,則是以鏈表方式解決沖突。
2.而當沖突大於等於8時,就會將沖突的Entry轉換為紅黑樹進行存儲。
3.而又當數量小於6時,則又轉化為鏈表存儲。
而在HashTable中, 都是以鏈表方式存儲。
----------------------------------------------------------------------------------
版權聲明:本文為CSDN博主「xuhuaabc」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/xuhuaabc/article/details/91475761