什麼是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...