公衆號:saysayJava,敬請支持。java
上一篇文章咱們介紹了HashMap的底層實現,但還遺留了一點內容,咱們再回顧一下上一篇文章裏說的內容算法
執行完紅框裏的代碼,personMap裏放入了8個元素,放置完成後在堆內存表現以下圖數組
若是忽略底層實現細節,是這樣的app
在Map中,一個key,對應了一個value,若是key的值已經存在,Map會直接替換value的內容,來看一下源碼中是怎麼實現的,來看如下代碼spa
Person oldPerson1 = personMap.put("張三", new Person("新張三", 21)); Person oldPerson2 = personMap.put("孫七", new Person("新孫七", 32)); System.out.println("oldPerson1.getName() :" + oldPerson1.getName()); System.out.println("oldPerson2.getName() : " + oldPerson2.getName()); System.out.println("personMap.size() : " + personMap.size());
new了一個Person「新張三」,注意,key依然是張三,看一下源碼debug
放入「新張三」時,會執行以上代碼一、二、5code
if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null);
上面這段代碼在上一篇文章已經改寫過了,改寫後的代碼以下:對象
i = (n - 1) & hash;//hash是傳過來的,其中n是底層數組的長度,用&運算符計算出i的值 p = tab[i];//用計算出來的i的值做爲下標從數組中元素 if(p == null){//這兒P不爲null,因此下面這行代碼不會執行。 tab[i] = newNode(hash, key, value, null);//這行代碼不會執行 }
很簡單,直接在底層數組裏取值賦值給p,因爲p不爲null,執行else裏的邏輯blog
Node<K,V> e; K k; if (p.hash == hash && //若是hash值相等,key也相等,或者equals相等,賦值給e ((k = p.key) == key || (key != null && key.equals(k)))) e = p;//賦值給e
又看到了熟悉的equals方法,這裏咱們hash值相等,key的值也相等,條件成立,把值賦值給e。(若是key的值不相等,就比較equals方法,也就是說,就算key是一個新new出來的對象,只要知足equals,也視爲key相同)內存
if (e != null) { // existing mapping for key V oldValue = e.value;//定義一個變量來存舊值 if (!onlyIfAbsent || oldValue == null) e.value = value;//把value的值賦值爲新的值 afterNodeAccess(e); return oldValue;//返回的值 }
這段代碼就比較簡單了,用新的value替換舊value並返回舊的value。畫一下圖
再new一個Person「新孫七」並put到personMap中,注意,key依然是「孫七」,會執行圖17-2裏的一、二、三、四、5,因爲二、3不知足條件,實際執行的是一、四、5,1這一步已經說過了,重點說一下4這一步
for (int binCount = 0; ; ++binCount) {//循環 if ((e = p.next) == null) {//若是循環到最後也沒找到,把元素放到最後 p.next = newNode(hash, key, value, null);//把元素放到最後 if (binCount >= TREEIFY_THRESHOLD - 1) //若是長度超>=8,轉換成紅黑樹 treeifyBin(tab, hash);//轉換成紅黑樹 break; } if (e.hash == hash && //這段代碼和第2步同樣 ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e;//若是hash值相等,key也相等或者equals相等,賦值給e } } }
其實就是循環鏈表的節點,直到找到"孫七"這個key,而後執行圖17-2裏的第5步,若是找不到,就添加到最後,這裏咱們key是「孫七」,在鏈表中找到元素替換value便可,再畫一下圖
最後來看看放到樹裏的方法putTreeVal,因爲樹的內容咱們還沒涉及到,下面只標註出了關鍵代碼
和鏈表相似,循環(遍歷)樹的節點,若是找到節點,返回節點,執行圖17-2裏的第5步更新value。若是循環完整顆數都找不到相應的key,添加新節點。
最後咱們看一下本文初那段示例代碼的執行結果:
雖然元素已經替換成新的值,但示例中打印的是替換前的值,元素個數仍是8不變,debug看一下,是否是value更新成功了
更新已經成功。
結合上一篇內容,作一個總結,在hashMap中放入(put)元素,有如下重要步驟:
一、計算key的hash值,算出元素在底層數組中的下標位置。
二、經過下標位置定位到底層數組裏的元素(也有多是鏈表也有多是樹)。
三、取到元素,判斷放入元素的key是否==或equals當前位置的key,成立則替換value值,返回舊值。
四、若是是樹,循環樹中的節點,判斷放入元素的key是否==或equals節點的key,成立則替換樹裏的value,並返回舊值,不成立就添加到樹裏。
五、不然就順着元素的鏈表結構循環節點,判斷放入元素的key是否==或equals節點的key,成立則替換鏈表裏value,並返回舊值,找不到就添加到鏈表的最後。
精簡一下,判斷放入HashMap中的元素要不要替換當前節點的元素,key知足如下兩個條件便可替換:
一、hash值相等。
二、==或equals的結果爲true。
因爲hash算法依賴於對象自己的hashCode方法,因此對於HashMap裏的元素來講,hashCode方法與equals方法很是的重要,這也是在說說Java裏的equals(中)一文中強調重寫對象的equals方法必定要重寫hashCode方法的緣由,不重寫的話,放到HashMap中可能會得不到你想要的結果!本示例中放入的key是String類型的,String這個類已經重寫了hashCode方法,有興趣的朋友能夠自行查看源碼。