JDK1.7版本中的HashMap
以下講解基於JDK1.7
HashMap底層是一個數組,哈希值相同的元素放在數組中的相同的位置,多個相同哈希值的元素形成一個鏈表。也就是說,元素的組織形式是單向鏈表。
下面從put、get、remove這三個方法分析一下源代碼,看看HashMap增刪查改是怎麽做的。
構造HashMap對象的時候做了初始化,指定默認的初始容量(數組長度)和增長因子
接下來,從put開始分析
從上面三段代碼可以看出添加一個元素的基本流程:
(1)HashMap的key值允許為null,而且key為null的元素放在數組中下標為0的位置
(2)根據待插入元素的key值計算出一個哈希值,然後根據這個哈希值和數組的長度計算該元素將要放置的位置(PS:下標)。如果這個位置為空,那麽直接插入。否則,遍歷該位置上的鏈表,依次比較他們的key值是否相同,如果相同,則將用新值替換舊值,然後返回就值。
(3)正常插入,將待插入的元素放置在鏈表的頭部,然後將其指向原先的鏈表頭部(即:原先放置在待插入位置的元素)。也就是說,新插入的元素是放在頭部的,我覺得這樣做的好處可能是根據LRU的原則減少遍歷的次數。
(4)有一種特殊情況是,擴容。
擴容就是,將原數組中的每個位置的元素都遷移到新數組中,在遷移到新數組的過程中同樣先計算哈希值,然後得出將要放置在新數組中那個位置上。鏈表的遷移過程相當於將原先的鏈表倒置,先將頭部的元素遷移過去,然後將下一個元素遷移過去,令next元素的next指向新數組位置上的元素,最終呈現出來的效果就是鏈表倒置。
接下來看get操作
get操作比較簡單:
(1)根據key算哈希值,進而得出元素可能的位置,然後遍歷該位置上的鏈表,比較key值是否相同,相同則返回,否則返回null
最後是remove
刪除也相對比較簡單:
(1)刪除元素所在位置,遍歷鏈表,比較key值,找到待刪除元素以後,如果當前只有一個元素,直接刪除,此位置置位NULL,否則將前面元素的next指向後面元素。
HashMap在並發情況下存在的問題(並發就是沒法保證順序)
(1)插入的元素可能被覆蓋
假設有兩個線程都執行到這裏,線程1它的key=A,value=aaa,線程2它的key=B,value=bbb。
假設i=1,那麽線程1執行的時候table[1]=new Entry<>(1234, "A", "aaa", null);
等到線程2執行的時候table[1]=new Entry<>(1234, "B", "bbb", null);
於是乎,線程1插入的數據就丟失了(或者說是被覆蓋了)
(2)put的時候,鏈表可能形成環形數據結構,導致如果查找一個不存在的元素時死循環
那麽環狀是怎麽形成的呢?發生在擴容的時候。請看圖
假設有兩個元素A和B,它們的關系是A.next=B,B.next=null
大概就是下面這個樣子
假設有兩個線程,線程-1和線程-2,它們在執行插入的時候都發現需要擴容,於是乎都開始擴容。
當然,擴容是在它們自己的內存中進行的。假設線程-1完成對A元素的遷移後準備對B進行遷移並執行到Entry<K,V> next = e.next;時還沒執行時線程被掛起了。執行到線程-2先執行完擴容,於是擴容後的指向關系變成了這個樣子:B.next=A,A.next=null
特別註意,看圖上畫的好像是元素直接放到數組的某個位置,但我們要知道,其它放的是元素的地址,也就是說元素本身的位置不變,修改的只是指針指向。盡管線程-2構造的新數組對線程-1而言是不可見的,但是不可否認,線程-2在擴容過程中已經將A和B的指向關系修改了,也就是說,此時,B是指向A的,這一點對線程-1而言是可見的。
接下來,線程-1醒來,繼續執行
while(null != e) {
Entry<,> next = e.; (rehash) { e.= == e.? : hash(e.); } i = (e., newCapacity); e.= newTable[i]; newTable[i] = e; e = next; }
此時,對照代碼應該是這樣的
經過這一遍,現在在新數組中的指向關系變成:B-->A-->NULL
緊接著,因為e已經是A了,所以null != e,於是再執行一遍
然後就變成這樣了
接下來,麻煩來了。
查找C,經過計算C應該與A、B在數組的同一個位置,於是遍歷鏈表
於是,通過A找到B,通過B又找到A,通過A又找到B,通過B又找到A,如此反復,永遠都不為null,死循環
終於講明白了
最後,再提一點,就是hash方法,字符串和非字符串算哈希值的方法是不一樣的
參考:
http://blog.csdn.net/zhuqiuhui/article/details/51849692
https://www.cnblogs.com/binyue/p/3726403.html
本文出自 “不要亂摸” 博客,請務必保留此出處http://5880861.blog.51cto.com/5870861/1984059
JDK1.7版本中的HashMap