上一篇文章我們介紹了HashMap的底層實(shí)現(xiàn),但還遺留了一點(diǎn)內(nèi)容,我們?cè)倩仡櫼幌律弦黄恼吕镎f(shuō)的內(nèi)容
執(zhí)行完紅框里的代碼景醇,personMap里放入了8個(gè)元素,放置完成后在堆內(nèi)存表現(xiàn)如下圖
如果忽略底層實(shí)現(xiàn)細(xì)節(jié)吝岭,是這樣的
在Map中三痰,一個(gè)key,對(duì)應(yīng)了一個(gè)value窜管,如果key的值已經(jīng)存在散劫,Map會(huì)直接替換value的內(nèi)容,來(lái)看一下源碼中是怎么實(shí)現(xiàn)的幕帆,來(lái)看以下代碼
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了一個(gè)Person“新張三”获搏,注意,key依然是張三失乾,看一下源碼
放入“新張三”時(shí)常熙,會(huì)執(zhí)行以上代碼1、2碱茁、5
iif ((p = tab[i = (n - 1) & hash]) == null)
? ? tab[i] = newNode(hash, key, value, null);
上面這段代碼在上一篇文章已經(jīng)改寫(xiě)過(guò)了裸卫,改寫(xiě)后的代碼如下:
i = (n - 1) & hash;//hash是傳過(guò)來(lái)的,其中n是底層數(shù)組的長(zhǎng)度纽竣,用&運(yùn)算符計(jì)算出i的值
p = tab[i];//用計(jì)算出來(lái)的i的值作為下標(biāo)從數(shù)組中元素
if(p == null){//這兒P不為null墓贿,所以下面這行代碼不會(huì)執(zhí)行茧泪。
? ? tab[i] = newNode(hash, key, value, null);//這行代碼不會(huì)執(zhí)行
}
很簡(jiǎn)單,直接在底層數(shù)組里取值賦值給p聋袋,由于p不為null队伟,執(zhí)行else里的邏輯
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方法,也就是說(shuō)硝逢,就算key是一個(gè)新new出來(lái)的對(duì)象姨拥,只要滿(mǎn)足equals,也視為key相同)
if (e != null) { // existing mapping for key
? ? V oldValue = e.value;//定義一個(gè)變量來(lái)存舊值
? ? if (!onlyIfAbsent || oldValue == null)
? ? e.value = value;//把value的值賦值為新的值
? ? afterNodeAccess(e);
? ? return oldValue;//返回的值
}
這段代碼就比較簡(jiǎn)單了渠鸽,用新的value替換舊value并返回舊的value叫乌。畫(huà)一下圖
再new一個(gè)Person“新孫七”并put到personMap中,注意徽缚,key依然是“孫七”憨奸,會(huì)執(zhí)行圖17-2里的1、2凿试、3排宰、4、5那婉,由于2板甘、3不滿(mǎn)足條件,實(shí)際執(zhí)行的是1详炬、4盐类、5,1這一步已經(jīng)說(shuō)過(guò)了呛谜,重點(diǎn)說(shuō)一下4這一步
for (int binCount = 0; ; ++binCount) {//循環(huán)
? ? if ((e = p.next) == null) {//如果循環(huán)到最后也沒(méi)找到在跳,把元素放到最后
? ? ? ? p.next = newNode(hash, key, value, null);//把元素放到最后
? ? ? ? if (binCount >= TREEIFY_THRESHOLD - 1) //如果長(zhǎng)度超>=8,轉(zhuǎn)換成紅黑樹(shù)
? ? ? ? ? ? treeifyBin(tab, hash);//轉(zhuǎn)換成紅黑樹(shù)
? ? ? ? ? ? break;
? ? ? ? }
? ? ? ? if (e.hash == hash && //這段代碼和第2步一樣
? ? ? ? ? ? ((k = e.key) == key || (key != null && key.equals(k))))
? ? ? ? ? ? break;
? ? ? ? ? ? p = e;//如果hash值相等呻率,key也相等或者equals相等硬毕,賦值給e
? ? ? ? }
? ? }
}
其實(shí)就是循環(huán)鏈表的節(jié)點(diǎn),直到找到"孫七"這個(gè)key礼仗,然后執(zhí)行圖17-2里的第5步吐咳,如果找不到逻悠,就添加到最后,這里我們key是“孫七”韭脊,在鏈表中找到元素替換value即可童谒,再畫(huà)一下圖
最后來(lái)看看放到樹(shù)里的方法putTreeVal,由于樹(shù)的內(nèi)容我們還沒(méi)涉及到沪羔,下面只標(biāo)注出了關(guān)鍵代碼
和鏈表類(lèi)似饥伊,循環(huán)(遍歷)樹(shù)的節(jié)點(diǎn),如果找到節(jié)點(diǎn)蔫饰,返回節(jié)點(diǎn)琅豆,執(zhí)行圖17-2里的第5步更新value。如果循環(huán)完整顆數(shù)都找不到相應(yīng)的key篓吁,添加新節(jié)點(diǎn)茫因。
最后我們看一下本文初那段示例代碼的執(zhí)行結(jié)果:
雖然元素已經(jīng)替換成新的值,但示例中打印的是替換前的值杖剪,元素個(gè)數(shù)還是8不變冻押,debug看一下,是不是value更新成功了
更新已經(jīng)成功盛嘿。
結(jié)合上一篇內(nèi)容洛巢,做一個(gè)總結(jié),在hashMap中放入(put)元素次兆,有以下重要步驟:
1稿茉、計(jì)算key的hash值,算出元素在底層數(shù)組中的下標(biāo)位置类垦。
2狈邑、通過(guò)下標(biāo)位置定位到底層數(shù)組里的元素(也有可能是鏈表也有可能是樹(shù))城须。
3蚤认、取到元素,判斷放入元素的key是否==或equals當(dāng)前位置的key糕伐,成立則替換value值砰琢,返回舊值。
4良瞧、如果是樹(shù)陪汽,循環(huán)樹(shù)中的節(jié)點(diǎn),判斷放入元素的key是否==或equals節(jié)點(diǎn)的key褥蚯,成立則替換樹(shù)里的value挚冤,并返回舊值,不成立就添加到樹(shù)里赞庶。
5训挡、否則就順著元素的鏈表結(jié)構(gòu)循環(huán)節(jié)點(diǎn)澳骤,判斷放入元素的key是否==或equals節(jié)點(diǎn)的key,成立則替換鏈表里value澜薄,并返回舊值为肮,找不到就添加到鏈表的最后。
精簡(jiǎn)一下肤京,判斷放入HashMap中的元素要不要替換當(dāng)前節(jié)點(diǎn)的元素颊艳,key滿(mǎn)足以下兩個(gè)條件即可替換:
1、hash值相等忘分。
2棋枕、==或equals的結(jié)果為true。
由于hash算法依賴(lài)于對(duì)象本身的hashCode方法妒峦,所以對(duì)于HashMap里的元素來(lái)說(shuō)戒悠,hashCode方法與equals方法非常的重要,這也是在說(shuō)說(shuō)Java里的equals(中)一文中強(qiáng)調(diào)重寫(xiě)對(duì)象的equals方法一定要重寫(xiě)hashCode方法的原因舟山,不重寫(xiě)的話绸狐,放到HashMap中可能會(huì)得不到你想要的結(jié)果!本示例中放入的key是String類(lèi)型的累盗,String這個(gè)類(lèi)已經(jīng)重寫(xiě)了hashCode方法寒矿,有興趣的朋友可以自行查看源碼。
轉(zhuǎn)載出自:https://zhuanlan.zhihu.com/p/28587782