HashMap工作原理及什么時候用到的紅黑樹:
在jdk 1.7中,HashMap采用位桶+鏈表實現,即使用鏈表處理沖突,同一hash值的鏈表都存儲在一個鏈表里。但是當位於一個桶中的元素較多,即hash值相等的元素較多時,通過key值依次查找的效率較低。
在jdk 1.8中,HashMap采用位桶+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換為紅黑樹,這樣大大減少了查找時間。
原理:數組中的每一個元素所在的位置相當於一個位桶,添加元素的時候,首先計算元素key的hash值,確定插入數組中的位置(也就是哪個桶中),如果存在相同的hash值,則放在同一個桶中(元素位置)形成鏈表,當鏈表長度超過閾值(8)時,將鏈表轉換為紅黑樹;
HashMap的內部結構:
HashMap 底層是基於數組和鏈表實現的,如圖所示,其中兩個重要的參數:容量和負載因子;容量的默認大小是 16,負載因子是 0.75,當 HashMap
的 size > 16*0.75
時就會發生擴容(容量和負載因子都可以自由調整)
內部包含了一個Node類型的數組 table(Entry<K,V>[] table為jdk 1.7中)。
transient Node<K,V>[] table;
Node存儲着鍵值對。它包含了四個字段,從 next 字段我們可以看出Node是一個鏈表。即數組中的每個位置被當成一個桶,一個桶存放一個鏈表。HashMap 使用拉鏈法來解決沖突,同一個鏈表中存放哈希值相同的Node;
拉鏈法的工作原理(解決hash沖突):
新建一個 HashMap,默認大小為 16;
- 插入 <K1,V1> 鍵值對,先計算 K1 的 hashCode 為 115,使用除留余數法得到所在的桶下標 115%16=3。
- 插入 <K2,V2> 鍵值對,先計算 K2 的 hashCode 為 118,使用除留余數法得到所在的桶下標 118%16=6。
- 插入 <K3,V3> 鍵值對,先計算 K3 的 hashCode 為 118,使用除留余數法得到所在的桶下標 118%16=6,插在 <K2,V2> 前面。
- 應該注意到鏈表的插入是以頭插法方式進行的,例如上面的 <K3,V3> 不是插在 <K2,V2> 后面,而是插入在鏈表頭部;
查找需要分成兩步進行:
- 計算鍵值對所在的桶;
- 在鏈表上順序查找,時間復雜度顯然和鏈表的長度成正比;
HashMap 允許插入鍵為 null 的鍵值對。但是因為無法調用 null 的 hashCode() 方法,也就無法確定該鍵值對的桶下標,只能通過強制指定一個桶下標來存放。HashMap 使用第 0 個桶存放鍵為 null 的鍵值對。
注意:
-
在並發環境下使用
HashMap
容易出現死循環。 -
並發場景發生擴容,調用
resize()
方法里的rehash()
時,容易出現環形鏈表。這樣當獲取一個不存在的key
時,計算出的index
正好是環形鏈表的下標時就會出現死循環。