Java集合包(六)——Map實現類之HashMap與HashTable 原理分析
一:HashMap特徵
1、HashMap 是一個散列表,它儲存的內容是鍵值對(key-value)對映。
2、HashMap是無序的。
3、HashMap是非執行緒安全的,只適合在單執行緒環境下使用。
二:HashMap繼承與實現關係
1、繼承層次
java.lang.Object ↳ java.util.AbstractMap<K, V> ↳ java.util.HashMap<K, V>
2、類定義與實現
public class HashMap<K,V> extendsAbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { }
繼承AbstractMap,實現了Map、Cloneable、java.io.Serializable介面,具備了通用的鍵值對操作能力、克隆能力、序列化能力。
三:HashMap原理
HashMap的重要成員與概念解析
1、table陣列:一個Entry[]陣列,每個陣列位置稱之為一個“桶”,每個桶位都對應一個hash值,每個桶位儲存一個Entry例項。而Entry例項實際上是一個單向連結串列,每個連結串列節點儲存一個鍵值對。
在jdk1.8之前,Entry陣列中儲存的是連結串列的表頭,一個連結串列儲存hash值相同的鍵值對們。鍵值對的hash值相同稱之為“hash衝突”,解決hash衝突有多種方法,HashMap採用的是拉鍊法。
1)拉鍊法
這種方法的思路是:把同一個雜湊槽(在我們這裡就是陣列的每一個桶位)中的所有元素放到一個連結串列中,也就是說:把hash值相同的鍵值對,儲存到同一個連結串列中。
往HashMap中put一個元素時:首先通過雜湊函式計算鍵值對的hash值,得到其在table陣列中的桶位。如果該桶位中沒有元素,則生成一個Entry例項儲存鍵值對後插入該桶位;
如果有元素,則檢查key值的唯一性:如果key值和我們要插入的資料不一樣,則生成一個Entry例項儲存鍵值對後,插入到該桶位的連結串列中;如果存在和我們要插入資料相同key的鍵值對,我們就把value進行更換即可。
2)再雜湊法
這種方法的思路是:當出現hash值相同的鍵值對時,進行再次hash計算,直到出現不衝突的位置為止。這樣會導致時間成本增加,不採用。
3)開放地址法
這種方法的思路是:插入一個元素的時候,先通過雜湊函式進行判斷,若是發生雜湊衝突,就以當前地址為基準,根據再定址的方法(探查序列),去尋找下一個地址,若發生衝突再去尋找,直至找到一個為空的地址為止。
所以這種方法又稱為再雜湊法。
jdk1.8引入了新的實現方式來解決hash衝突,當某個entry連結串列長度大於8時,會轉化成紅黑樹進行儲存;當某個entry樹的小於6個元素時,又會轉回連結串列儲存。
這樣做的原因是:
1)原因:
紅黑樹的平均查詢長度是log(n),當長度為8,查詢長度為log(8)=3;
連結串列的平均查詢長度為n/2,當長度為8時,平均查詢長度為8/2=4,此時開始有轉換成樹的必要;
連結串列長度如果是小於等於6,6/2=3,因此小於等於6時連結串列形式的速度已足夠快,雖然紅黑樹的速度會更快,但是轉化為樹結構也需要時間開銷,因此最終效能表現差距不大,無謂再轉成紅黑樹。
2)選擇6和8的原因是:
中間有個差值7可以防止連結串列和樹之間頻繁的轉換。
3)引入紅黑樹而不是AVL平衡二叉樹的原因
紅黑樹相比avl樹,在檢索的時候效率其實差不多,都是通過平衡來二分查詢。
但對於插入刪除等操作,紅黑樹不像avl樹一樣追求絕對的平衡,他允許區域性很少的不完全平衡,省去了很多沒有必要的平衡調整操作,因此效能更優。
2、初始容量:容量 是指雜湊表中桶的數量,一個桶儲存一個鍵值對。
初始容量 指雜湊表在建立時的一開始的容量。
3、載入因子loadFactor:指觸發雜湊表擴容的一個當前大小的臨界值。
當雜湊表中的條目數超出了載入因子與當前容量的乘積時,則要對該雜湊表進行 rehash 操作進行擴容,將容量擴大一倍。
預設載入因子是 0.75, 這是在時間和空間成本上尋求一種折衷。載入因子過高雖然減少了空間開銷,但同時也增加了查詢成本。
4、閾值threshold:用於判斷是否需要調整HashMap的容量,threshold="容量*載入因子",當HashMap中儲存資料的數量達到threshold時,就需要將HashMap的容量加倍。
5、size:儲存當前鍵值對的數量,即HashMap的大小。
6、modCount:實現fail-fast機制的判斷依據。
四:HashMap關鍵方法