JDK原始碼學習 HashMap

2021-08-19 19:26:46 字數 2906 閱讀 5557

什麼是hash?

hash的意思是「雜湊」,音譯做「雜湊」,輸入乙個任意長度的資料,進過雜湊運算之後,輸出一段固定長度的資料,作為輸入資料的指紋,輸出的結果就是雜湊值。一般來說輸入資料的空間遠遠大於輸出的雜湊值的空間,輸入不同的資料可能會產生相同的雜湊值,所以很難從雜湊值來逆向推出輸入值是什麼。雜湊函式本質上是乙個壓縮演算法,它不同長度的訊息壓縮成為固定長度的訊息。

雜湊函式有乙個特點:對於同乙個雜湊函式,如果計算出來的雜湊值不同,那麼輸入的資料一定不同,但是輸入的資料不同,通過雜湊函式計算出來的雜湊值可能相同。

如果輸入不同的資料,卻產生了相同的雜湊值,就認為發生了雜湊衝突。產生衝突就要解決,通常的解決方法有開放位址法、鏈位址法、再雜湊法。

開放位址法:當發生雜湊衝突時,就在雜湊表中尋找寫乙個雜湊位址,直到找到乙個空的雜湊位址為止,只要雜湊表足夠大,總能找到空的雜湊位址

鏈位址法:是用了陣列和鍊錶這兩種資料結構。陣列的優點是查詢很方便,通過訪問資料下標就可以訪問對應的元素,缺點是插入和刪除元素較慢,如果在陣列的中間進行插入,很多元素的記憶體位址都需要移動,效率比較低。 鍊錶的優點是插入、刪除很方便,只需要修改對應元素的指標就可以了,缺點是查詢不方便。 在鏈位址法里就是結合陣列和鍊錶的優點,用陣列做查詢,用鍊錶做插入和刪除。

把雜湊值相同的元素連線成乙個鍊錶,鍊錶的頭結點存到陣列裡,這樣陣列的每乙個但願就對應乙個鍊錶。

再雜湊法:當雜湊位址衝突時,使用其他的hash函式計算另乙個hash位址,直到不再產生hash衝突

hashmap

在jdk1.7原始碼的hashmap裡,使用的鏈位址法,構造了entry型別的陣列,儲存entry物件組成的鍊錶,乙個entry物件包含key-value對。

儲存元素

如果想在hashmap中新增entry物件,需要使用put方法。

public v put(k key, v value) 

if (key == null)

return putfornullkey(value);

int hash = hash(key);

int i = indexfor(hash, table.length);

for (entrye = table[i]; e != null; e = e.next)

}modcount++;

//根據計算出來的hash值,找到陣列table對應的下標i,把key-value對放進去

addentry(hash, key, value, i);

return null;

}

在put方法中,首先根據entry物件的key計算它的hash值,由這個hash值確定這個物件在陣列中的儲存位置(也就是在陣列中的下標)。如果當前位置上為空,直接把元素放在這裡就可以了。如果當前儲存位置上已經有乙個元素存在了,說明這兩個entry元素的key計算的hash值相同,所以儲存位置才會相同。如果這兩個entry的key通過equals方法比較之後返回true,那麼用新加入entry的value覆蓋原來entry的value,key的值不覆蓋。如果這兩個entry的key通過equals方法比較之後範湖false,那麼就把entry元素以鍊錶的形式存放,新加入的entry元素放在鍊錶的頭部,原來的元素放在鍊錶的尾部。

讀取元素

在hashmap中讀取元素需要使用get方法。

public v get(object key) 

final entrygetentry(object key)

int hash = (key == null) ? 0 : hash(key);

for (entrye = table[indexfor(hash, table.length)];

e != null;

e = e.next)

return null;

}

首先判斷key是否為空,為空就返回空,不為空就進入getentry方法中,首先計算key的hash值,根據hash值定位到table陣列的相應位置,然後在通過比較key在鍊錶中找到需要的元素。

hashmap的擴大容量的機制

陣列的初始容量是1左四位,也就是2^4=16

static final int default_initial_capacity = 1 << 4; // aka 16

當hashmap中的元素越來越多,出現hash衝突的概率越來越高,因為陣列的長度是固定的,為了提高查詢的效率,需要對陣列進行擴大容量。具體什麼時候進行擴大容量,要看loadfactor。loadfactor的預設值是0.75.

/**

* the load factor used when none specified in constructor.

*/static final float default_load_factor = 0.75f;

當陣列元素個數超過陣列容量乘以loadfactor的時候,就把容量擴大為原來的兩倍。

為什麼loadfactor是0.75,而不是其他的的數?

這要理解loadfactor裝載因子的意義,裝載因子衡量的是乙個hash表空間的使用程度,他的值越大表示空間利用率越高,他的值越小表明利用率越低。由於hashmap使用的鏈位址法,查詢乙個元素的平均時間是常數級別的,裝載因子越大,對空間的利用就越充分,但是也會導致查詢的效率降低;如果裝載因子太小,hash表太稀疏,會造成空間的浪費。因此對時間和空間效率做了一下平衡,把裝載因子取值為0.75.

擴容的時候為什麼是2倍,不是1.5或3倍?

者主要是出於效能方面的考慮,設計成2的倍數可以通過位運算完成,這樣比去乘1.5,乘3操作要快。至於為什麼位運算比較快?因為它直接對記憶體資料進行操作,而不需要轉換成十進位制在操作,所以效率高。

JDK原始碼學習 HashMap(一)

public class hashmapextends abstractmapimplements map,cloneable serializable 類定義 hashmap繼承了abstractmap,實現map,cloneable,serializable 介面 abstractmap 實現了...

JDK原始碼學習01 HashMap原始碼學習

hashmap中直接注意的細節 紅黑樹長度小於閾值 yu 4聲 6轉化成兩邊 鍊錶長度大於閾值8 且table的長度大於等於64 才樹化 僅滿足鍊錶長度大於閾值8只會呼叫resize擴容 為什麼是6和8呢,而不設定成一樣呢,因為為了防止在邊界反覆橫跳,浪費效能if tab null n tab.le...

JDK原始碼之HashMap

部分重要屬性 存放key,value的陣列 transient node table 存放entry的set transient set entryset hashmap的大小 預設16 transient int size 修改次數 transient int modcount 擴擴容閾值capa...