HashMap深度解析

2021-09-23 05:47:54 字數 3642 閱讀 8665

簡介:

基於雜湊表的 map 介面的實現。此實現提供所有可選的對映操作,並允許使用 null 值和 null 鍵。(除了非同步和允許使用 null 之外,hashmap 類與 hashtable 大致相同。)此類不保證對映的順序,特別是它不保證該順序恆久不變。 此實現假定雜湊函式將元素適當地分布在各桶之間,可為基本操作(get 和 put)提供穩定的效能。迭代 collection 檢視所需的時間與 hashmap 例項的「容量」(桶的數量)及其大小(鍵-值對映關係數)成比例。所以,如果迭代效能很重要,則不要將初始容量設定得太高(或將載入因子設定得太低)

類圖結構:

特性:

1 hashmap 允許key,value都為空。

2 hashmap 在操作鍵值對時不是執行緒安全的。

3 hashmap 底層以陣列+單鏈表(節點數大於8則,單鏈表轉紅黑樹)儲存鍵值對。

4 hashmap實際就是 node table 陣列,node節點實現了map.entry。

主要方法:

1 put 方法詳解:

public v put(k key, v value) 

// 根據key計算hash值

static final int hash(object key)

// 根據hash值,將鍵值對存放到hashmap中

final v putval(int hash, k key, v value, boolean onlyifabsent,

boolean evict)

// 如果在遍歷過程中找到 key 相同時直接退出遍歷。

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

break;

p = e;}}

// 如果存在相同的 key,那就需要將值覆蓋。

v oldvalue = e.value;

if (!onlyifabsent || oldvalue == null)

e.value = value;

afternodeaccess(e);

return oldvalue;}}

++modcount;

// 判斷是否需要進行擴容。

if (++size > threshold)

resize();

afternodeinsertion(evict);

return null;

}

根據key計算陣列下標的步驟如下:

(1)h = key.hashcode():根據key值計算hashcode。這裡的 hashcode() 是乙個 native 方法,根據一定的規則將與物件相關的資訊(比如物件的儲存位址,物件的字段等)對映成乙個數值,這個數值稱作為雜湊值。

(2)h >>> 16:將得到的hashcode無符號右移16位,低位移出(捨棄),高位的空位補符號位,即正數補0,負數補1。

(3 )(h = key.hashcode()) ^ (h >>> 16):h與h右移16位進行異或運算(兩者相等為0,不等為1)得到hash值。

(4)計算(n - 1) & hash 的值即為陣列下標。n代表陣列大小,初始化時預設是16。

2 get 方法詳解:

public v get(object key) 

final nodegetnode(int hash, object key) while ((e = e.next) != null);}}

// 為空直接返回

return null;

}

3 resize 方法詳解:

// 初始化或加倍陣列大小 : 如果為null,則分配初始容量。否則,進行2次冪擴充套件。

final node resize()

// 擴容:oldcap*2,即把新的陣列容量 "newcap" 擴大2倍

else if ((newcap = oldcap << 1) < maximum_capacity &&

oldcap >= default_initial_capacity)

// 新的擴容閾值同樣擴大2倍

newthr = oldthr << 1;

}// 當 oldthr > 0 就說明使用者呼叫了有參構造方法(指定了初始容量,並被構造方法 "快取" 到了threshold中了)

else if (oldthr > 0)

newcap = oldthr;

// 初始化的陣列容量為預設的 16,初始化的擴容閾值為預設的 16 * 0.75

else

// 第一次resize()時,計算擴容閾值

if (newthr == 0)

threshold = newthr;

@suppresswarnings()

node newtab = (node)new node[newcap];

table = newtab;

//不是第一次reizie()時

if (oldtab != null)

// 如果判斷不成立,那麼該元素的位址變為 原下標位置+oldcap,也就是lodcap最高位的1,在e.hash對應的位置上也為1,所以擴容後的位址改變了,在後面的**中會放到hihead中,最後賦值給newtab[j + oldcap]

else

} while ((e = next) != null);

if (lotail != null)

if (hitail != null) }}

}}return newtab;

}

上述 (e.hash & oldcap) == 0 即可將原桶中的資料分成2類:元素的位置要麼是在原位置,要麼是「j + oldcap」(當前位置索引+原容量的值)。這也是resize方法擴容後為什麼是原來的2倍的原因。

jdk1.7中,resize時,index取得時,全部採用重新hash的方式進行了。jdk1.8對這個進行了改善:

以前要確定index的時候用的是(e.hash & oldcap-1),是取模取餘,而這裡用到的是(e.hash & oldcap),它有兩種結果,乙個是0,乙個是oldcap,比如oldcap=8,hash是3,11,19,27時,(e.hash & oldcap)的結果是0,8,0,8,這樣3,19組成新的鍊錶,index為3;而11,27組成新的鍊錶,新分配的index為3+8;

jdk1.7中重寫hash是(e.hash & newcap-1),也就是3,11,19,27對16取餘,也是3,11,3,11,和上面的結果一樣,但是index為3的鍊錶是19,3,index為3+8的鍊錶是27,11,也就是說1.7中經過resize後資料的順序變成了倒敘,而jdk1.8沒有改變順序。

HashMap 原始碼深度解析

hashmap 的底層使用 陣列 單項鍊表,jdk1.8後,當鍊表過長時,會將鍊錶轉成紅黑樹,時間複雜度由 o n 變成了 o logn 同時,hashmap 是執行緒不安全的。public v put k key,v value a 對 key 求 hash 值。計算下標 通過 hash obje...

HashMap初步解析

hashmap集合 1 hashmap集合底層是雜湊表 雜湊表的資料結構。2 雜湊表是乙個怎樣的資料結構呢?雜湊表是乙個陣列和單向鍊錶的結合體。陣列 在查詢方面效率很高,隨機增刪方面效率很低。單向鍊錶 在隨機增刪方面效率較高,在查詢方面效率很低。雜湊表將以上的兩種資料結構融合在一起,充分發揮它們各自...

深度了解HashMap

基於hash演算法的圖,是一種資料結構。list set map queue都是資料結構 容器 資料結構 線性結構 陣列 列表list 佇列 棧 樹 二叉樹 b樹 堆 圖。特點 查詢非常快。什麼是hash?雜湊 或音譯雜湊 摘要演算法 把任意長度的輸入,通過雜湊演算法變成固定長度的輸出。不同的輸入,...