學習 HashMap 原理筆記

2021-09-01 13:31:17 字數 4486 閱讀 5411

感謝  ,學習了,寫寫筆記。

雜湊表。

雜湊表利用陣列根據下標查詢元素,一次定位就可以找到,複雜度為o(1)。

可以理解,雜湊表的主幹是陣列,通過某個函式把當前元素的關鍵字對映到陣列中的某個位置上,通過下標就可以完成定位。

雜湊衝突:

插入的時候,用雜湊函式算出來的對映下標插入元素的時候,發現該位置已經被占用。hashmap採用了鏈位址法解決雜湊衝突的,也就是陣列+鍊錶的方式,

hashmap 的主幹其實是乙個entry  陣列,entry 是hashmap 中的乙個靜態內部類:

static class entryimplements map.entry
前面說了解決雜湊衝突是鏈位址法, 所以entry存在指向下乙個entry  的引用,這個引用的存在就是為了解決雜湊衝突而存在的,插入的時候,如果發現函式對映的下標已經被占用,如果該位置的next 為空,就會在該位置生成乙個鍊錶。如果該位置已經存下鍊錶,就會遍歷該下標的鍊錶,如果有相等的元素就覆蓋,沒有就在鍊錶尾部新增新的元素。

結論,hashmap 的效能決定鍊錶的多少。鍊錶越少,效能越好。

jdk 1.7 的原始碼在上面部落格有講解很清楚,這裡寫些筆記:

執行新增操作時,根據key的hashcode進行hash()雜湊函式計算,確保均勻分布,再根據雜湊值獲取下標(indexfor 函式

),將值放入該位置。

即: hashcode(key的hashcode) ——>  h(通過神奇的hash函式獲取) ——>下標

這裡考慮的問題時擴容,什麼時候擴容????下面是hashmap 比較重要的變數:

transient int size; //實際鍵值對的個數 

/** 閾值 初始容量16*/

int threshold;

static final int default_initial_capacity = 1 << 4; // 初始容量

/** 負載因子*/

final float loadfactor;

static final float default_load_factor = 0.75f; //初始負載因子

/** hashmap是非執行緒安全的,在對hashmap進行迭代時,由於其他執行緒的參與導致hashmap的結構發生變化了(比如put,remove等操作),都會丟擲異常concurrentmodificationexception*/

transient int modcount;

**片段:

public v put(k key, v value) ,進行陣列填充(為table分配實際記憶體空間),入參為threshold,此時threshold為initialcapacity 預設是1<<4(24=16)

if (table == empty_table)

//如果key為null,儲存位置為table[0]或table[0]的衝突鏈上

if (key == null)

return putfornullkey(value);

int hash = hash(key);//對key的hashcode進一步計算,確保雜湊均勻

int i = indexfor(hash, table.length);//獲取在table中的實際位置

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

}modcount++;//保證併發訪問時,若hashmap內部結構發生變化,快速響應失敗

addentry(hash, key, value, i);//新增乙個entry

return null;

}/**用於為主幹陣列table在記憶體中分配儲存空間

*/private void inflatetable(int tosize)

/***rounduptopowerof2(tosize)可以確保capacity為大於或等於tosize的最接近tosize的二次冪,比如*tosize=13,則*capacity=16;to_size=16,capacity=16;to_size=17,capacity=32.

*/private static int rounduptopowerof2(int number)

/*** 新增節點

*/ void addentry(int hash, k key, v value, int bucketindex)

createentry(hash, key, value, bucketindex);

}/** 擴容

**/void resize(int newcapacity)

entry newtable = new entry[newcapacity];

transfer(newtable, inithashseedasneeded(newcapacity));

table = newtable;

threshold = (int)math.min(newcapacity * loadfactor, maximum_capacity + 1);

}/*** 將資料複製到新的陣列中

*/void transfer(entry newtable, boolean rehash)

int i = indexfor(e.hash, newcapacity);

//將當前entry的next鏈指向新的索引位置,newtable[i]有可能為空,有可能也是個entry鏈,如果是entry鏈,直接在鍊錶頭部插入。

e.next = newtable[i];

newtable[i] = e;

e = next;}}

}

新增元素時,當發生雜湊衝突並且size大於閾值的時候,需要進行陣列擴容,擴容時,新建乙個長度為之前陣列2倍的新的陣列,然後將原entry陣列中的元素全部複製到新的陣列,擴容後的新陣列長度為之前的2倍,所以擴容相對來說是個耗資源的操作。

1、為什麼hashmap的陣列長度一定是2的次冪?

從獲取下標的indexfor 函式開始說

static int indexfor(int h, int length)
假如 length 為16 ,那麼length 減1 就是15 ,二進位制為01111 ,無論h為何值,與運算出來的陣列下標都值取決於後面4位。

假設,h的低位是1010,length為16 ,indexfor 得到的index 二進位制依然是1010,無論後四位的值是多少,下標都是後四位。

但是,如果length為10,length-1 的二進位制為1001 ,與1010 得到的值為1000,index 為8,但是h的低位為1110 、1000、1101算出來的都是1000,這意味著進一步增加了碰撞的機率,增大了鍊錶數量,減慢了查詢的效率!這就違背演算法均勻分布的要求。

所以,只要通過hash演算法的h是均勻分布的,indexfor 算出的下標也肯定是均勻分布的。

從上面擴容的**可以看到,每次擴容,都要重新生成陣列的下標,length-1 的值由15(01111) 到 31(11111) ,二進位制僅僅差了一位,當h對應的那一位為0時,得到的新的陣列索引和老陣列索引一致(大大減少了之前已經雜湊良好的老陣列的資料位置重新調換,引用部落格觀點)。

2、為什麼重寫equals方法要重寫hashcode?

我們在進行get和put操作的時候,使用的key從邏輯上講是等值的(通過equals比較是相等的),但由於沒有重寫hashcode方法,所以put操作時,key(hashcode1)-->hash-->indexfor-->最終索引位置 ,而通過key取出value的時候 key(hashcode1)-->hash-->indexfor-->最終索引位置,由於hashcode1不等於hashcode2,導致沒有定位到乙個陣列位置而返回邏輯上錯誤的值null(也有可能碰巧定位到乙個陣列位置,但是也會判斷其entry的hash值是否相等,上面get方法中有提到。)

所以,在重寫equals的方法的時候,必須注意重寫hashcode方法,同時還要保證通過equals判斷相等的兩個物件,呼叫hashcode方法要返回同樣的整數值。而如果equals判斷不相等的兩個物件,其hashcode可以相同(只不過會發生雜湊衝突,應盡量避免)。

3、hashmap 和 hashtable的區別

b. hashmap是支援null鍵和null值的,而hashtable在遇到null時,會丟擲nullpointerexception異常。這並不是因為hashtable有什麼特殊的實現層面的原因導致不能支援null鍵和null值,這僅僅是因為hashmap在實現時對null做了特殊處理,將null的hashcode值定為了0,從而將其存放在雜湊表的第0個bucket中;

c. hashtable是同步的,hashmap不是,也就是說hashtable在多執行緒使用的情況下,不需要做額外的同步,而hashmap則不行;

d. hashtable已經被淘汰了,自己寫**不要再使用它。

詳細區別檢視:

HashMap底層實現原理(學習筆記)

看了一上午,原始碼看的頭疼,果斷放棄,放個鏈結吧 jdk1.8原始碼解析 只記錄下理解的幾個知識點 1.實現介面 map,2.hashmap是陣列 鍊錶的形式實現的,使用hash來計算在陣列的索引位置,再在鍊錶中使用equals來判斷位置。3.key和value都可以為null 4.和hashtab...

面試經典題學習筆記 hashmap實現原理

hashmap採用陣列 鍊錶的方式來儲存資料,儲存的資料為鍵值對的形式,包括乙個key,乙個value。根據key的值進行hashcode,算出乙個值,再對這個值取餘,一般選擇陣列的長度進行取餘,再得出乙個值,而後將這個值作為儲存位置的下標。但是會出現一些問題某些key最終取得的下標會相同,這時候應...

HashMap的學習筆記

static class entry implements map.entry public hashmap int initialcapacity,float loadfactor final entrygetentry object key 通過key的hashcode值計算hash值 int ...