1. 程式人生 > >HashMap與Hashtable區別及HashMap實現原理

HashMap與Hashtable區別及HashMap實現原理

    Map是編碼過程中經常使用到的容器,而HashMap和Hashtable都實現了Map的,所以我們往往會把兩者進行對比。

HashMap和Hashtable區別

  1. Hashtable是執行緒安全的,HashMap是非執行緒安全的。Hashtable是基於老的Diactionary類實現的,HashMap是Java 1.2引進Map介面後的重新實現。Hashtable的方法,進行了鎖同步,可以支行於多執行緒環境。HashMap需要程式設計人員自在己為其提供同步,才能執行多執行緒中。常用的方法是:利用Collections類的靜態的synchronizedMap()方法,它建立一個執行緒安全的Map物件或者是使用ConcurrentHashMap。
  2. 由於HashMap非是執行緒安全的,所以效能要明顯優於Hashtable。
    如果多執行緒情況下呢?Hashtable的實現與Collections的靜態方法synchronizedMap實現有點類似,使用synchronized來保證執行緒安全。所以如果線上程競爭激烈的情況下,效率就會非常低下,而且執行緒的安全性還緊限於get、put之類的簡單操作。這時ConcurrentHashMap可能是最好的選擇。
  3. 對空值處理:HashMap中允許有一個空的key,和任意個空的value,對Hashtable put空的key、value則會報錯。如果判斷HashMap是否存在某個key值,最準確的是使用containKey,而不是使用對get的值判空,因為你put的也許是個空值。
    HashMap為什麼會擁有較高的效能,如果你要定義一個用於類似存取的容器你會如何實現呢?或許你會這樣實現:     定義一個長鍵值對陣列,用於存取物件,存值時,直接將第size位設定為需要儲存的物件,取值時,遍歷這個陣列,將key等於你要值的鍵值對的value取出就可以了。好像挺簡單的,也許你已經發現問題了。每次取值都需要遍歷整個陣列,效能必然不高;陣列的長度需要定義成多大,如果put的值數量超過了map的總長度,怎麼辦? HashMap的實現原理     HashMap的底層主要是基於陣列和連結串列來實現的,它之所以有相當快的查詢速度主要是因為它是通過計算雜湊碼來決定儲存的位置。HashMap預定義了一個很長的陣列,每個陣列元素相當於一個取放存取物件的桶。這有點類似基數排序中用到的技巧,利用陣列的存取效能,達到空間換取時間的目的。但如果僅僅是這樣,就需要定義一個足夠長的陣列,而且還要找到key與陣列下標的一種對映關係。     關於定義一個足夠長的陣列這好解決,可以參考ArrayList的實現,先設定一個長度預設值,當存放的值個數多了時候,再對陣列進行擴容。為了在時間和空間上都能找到一個比較令人滿意的處理方式,通過雜湊碼來查詢函式下標。這又帶來一個問題,不同的物件的雜湊碼可能值是一樣的(不同物件可能equal,但hashcode可能一樣,反之不成立)。為了解決這種碰撞問題,我們不得不讓每個桶維繫一個連結串列,用於存放key值碰撞的物件。我們來看看HashMap的原始碼實現:
transient Entry[] table;//儲存元素的實體陣列
 
transient int size;//存放元素的個數
 
int threshold; //臨界值

final float loadFactor; //載入因子
 
transient int modCount;//被修改的次數

當實際大小超過臨界值時,會進行擴容threshold = 載入因子*容量。載入因子越大,填滿的元素越多,好處是,空間利用率高了,衝突的機會加大了.連結串列長度會越來越長,查詢效率降低。反之,載入因子越小,填滿的元素越少,好處是:衝突的機會減小了,但:空間浪費多了.表中的資料將過於稀疏(很多空間還沒用,就開始擴容了)。衝突的機會越大,則查詢的成本越高.因此,必須在 "衝突的機會"與"空間利用率"之間尋找一種平衡與折衷. 這種平衡與折衷本質上是資料結構中有名的"時-空"矛盾的平衡與折衷。如果機器記憶體足夠,並且想要提高查詢速度的話可以將載入因子設定小一點;相反如果機器記憶體緊張,並且對查詢速度沒有什麼要求的話可以將載入因子設定大一點。不過一般我們都不用去設定它,讓它取預設值0.75就好了。
    每個Entry物件都記錄Entry連結串列的下一個結點。當查詢的key值出現hash碰撞,我們就需要遍歷連結串列了。
    我們再來看看,hash碼是如何對映陣列下標的:
static int hash(int h) {
	// This function ensures that hashCodes that differ only by
	// constant multiples at each bit position have a bounded
	// number of collisions (approximately 8 at default load factor).
	h ^= (h >>> 20) ^ (h >>> 12);
	return h ^ (h >>> 7) ^ (h >>> 4);
}

static int indexFor(int h, int length) {
	return h & (length-1);
}

    雜湊表的容量一定要是2的整數次冪,這樣HashMap中則通過h&(length-1)的方法來代替取模,就實現了均勻的雜湊。同時在陣列進行擴容時,擴容前的元素的下標位置是不變的,避免了陣列元素的重排。但是,我們仍然要避免陣列的擴容,因為它依然很大的降低的了map的put效能,這也是HashMap定義載入因子的原因所在。
    那我們該如果初始化載入因子的值呢?這和Map值個數有關係,和Map的每個key值有關係。為了達到這種平衡,我們只能通過真實資料,場景進行,測試來獲得這個最佳的載入因子。如果不確定就用預設值好了,沒有更好的方法。