<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Map 集合怎么也有這么多坑?一不小心又踩了好幾個(gè)!

          共 3900字,需瀏覽 8分鐘

           ·

          2020-05-14 23:21


          本文公眾號(hào)來源:程序通事作者:樓下小黑哥本文已收錄至我的GitHub

          本文設(shè)計(jì)知識(shí)點(diǎn)如下:

          5f42a7a433d84fabb62cebeb24225a45.webp

          不是所有的 Map 都能包含  null

          這個(gè)踩坑經(jīng)歷還是發(fā)生在實(shí)習(xí)的時(shí)候,那時(shí)候有這樣一段業(yè)務(wù)代碼,功能很簡單,從 XML 中讀取相關(guān)配置,存入 Map 中。

          代碼示例如下:

          abcc50629fad4b1d1aa7e2cae5aded47.webp

          那時(shí)候正好有個(gè)小需求,需要改動(dòng)一下這段業(yè)務(wù)代碼。改動(dòng)的過程中,突然想到 HashMap 并發(fā)過程可能導(dǎo)致死鎖的問題。

          于是改動(dòng)了一下這段代碼,將 HashMap 修改成了 ConcurrentHashMap

          美滋滋提交了代碼,然后當(dāng)天上線的時(shí)候,就發(fā)現(xiàn)炸了。。。

          應(yīng)用啟動(dòng)過程發(fā)生 NPE 問題,導(dǎo)致應(yīng)用啟動(dòng)失敗。

          3ef985adf2ad3c788fff2c50aa2fb825.webp

          根據(jù)異常日志,很快就定位到了問題原因。由于 XML 某一項(xiàng)配置問題,導(dǎo)致讀取元素為 null,然后元素置入到 ConcurrentHashMap 中,拋出了空指針異常。

          這不科學(xué)啊!之前 HashMap 都沒問題,都可以存在 null,為什么它老弟 ConcurrentHashMap 就不可以?

          1d6ba8fd4ac5529dd8eb5ff9ae1bd0e5.webp

          翻閱了一下 ConcurrentHashMap#put 方法的源碼,開頭就看到了對(duì) KV 的判空校驗(yàn)。

          ab12cdad6efce9b6b3b9c1306a481c80.webp

          看到這里,不知道你有沒有疑惑,為什么 ConcurrentHashMapHashMap 設(shè)計(jì)的判斷邏輯不一樣?

          求助了下萬能的 Google,找到 Doug Lea 老爺子的回答:

          12720cc1610f0535196099ffc0e15b67.webp來源:http://cs.oswego.edu/pipermail/concurrency-interest/2006-May/002485.html

          總結(jié)一下:

          • null 會(huì)引起歧義,如果 value 為 null,我們無法得知是值為 null,還是 key 未映射具體值?
          • Doug Lea 并不喜歡 null,認(rèn)為 null 就是個(gè)隱藏的炸彈。

          上面提到 Josh Bloch 正是 HashMap 作者,他與 Doug Lea 在 null 問題意見并不一致。

          也許正是因?yàn)檫@些原因,從而導(dǎo)致 ConcurrentHashMapHashMap 對(duì)于 null 處理并不一樣。

          最后貼一下常用 Map 子類集合對(duì)于 null 存儲(chǔ)情況:

          d8ef2566cd7e3d1c56a26c798158f66f.webp

          上面的實(shí)現(xiàn)類約束,都太不一樣,有點(diǎn)不好記憶。其實(shí)只要我們在加入元素之前,主動(dòng)去做空指針判斷,不要在 Map 中存入 null,就可以從容避免上面問題。

          自定義對(duì)象為 key

          先來看個(gè)簡單的例子,我們自定義一個(gè) Goods 商品類,將其作為 Key 存在 Map 中。

          示例代碼如下:

          43fa52df453d48c7ac5b78cb48e4880e.webp

          上面代碼中,第二次我們加入一個(gè)相同的商品,原本我們期望新加入的值將會(huì)替換原來舊值。但是實(shí)際上這里并沒有替換成功,反而又加入一對(duì)鍵值。

          翻看一下 HashMap#put 的源碼:

          以下代碼基于 JDK1.7

          19796dcf48f6b1a44ad700175582c7e9.webp

          這里首先判斷 hashCode 計(jì)算產(chǎn)生的 hash,如果相等,再判斷 equals 的結(jié)果。但是由于 Goods對(duì)象未重寫的hashCodeequals 方法,默認(rèn)情況下 hashCode 將會(huì)使用父類對(duì)象 Object 方法邏輯。

          Object#hashCode 是一個(gè) native 方法,默認(rèn)將會(huì)為每一個(gè)對(duì)象生成不同 hashcode與內(nèi)存地址有關(guān)),這就導(dǎo)致上面的情況。

          所以如果需要使用自定義對(duì)象做為 Map 集合的 key,那么一定記得重寫hashCodeequals 方法。

          然后當(dāng)你為自定義對(duì)象重寫上面兩個(gè)方法,接下去又可能踩坑另外一個(gè)坑。

          83459dfc4aa68f041d3e319434775044.webp

          使用 lombok 的 EqualsAndHashCode 自動(dòng)重寫 hashCodeequals 方法。

          上面的代碼中,當(dāng) Map 中置入自定義對(duì)象后,接著修改了商品金額。然后當(dāng)我們想根據(jù)同一個(gè)對(duì)象取出 Map 中存的值時(shí),卻發(fā)現(xiàn)取不出來了。

          上面的問題主要是因?yàn)?get 方法是根據(jù)對(duì)象 的 hashcode 計(jì)算產(chǎn)生的 hash 值取定位內(nèi)部存儲(chǔ)位置。

          14c4984e1cbf4f62907746707d25bed1.webp

          當(dāng)我們修改了金額字段后,導(dǎo)致 Goods 對(duì)象 hashcode 產(chǎn)生的了變化,從而導(dǎo)致 get 方法無法獲取到值。

          通過上面兩種情況,可以看到使用自定義對(duì)象作為 Map 集合 key,還是挺容易踩坑的。

          所以盡量避免使用自定義對(duì)象作為 Map 集合 key,如果一定要使用,記得重寫 hashCodeequals 方法。另外還要保證這是一個(gè)不可變對(duì)象,即對(duì)象創(chuàng)建之后,無法再修改里面字段值。

          錯(cuò)用 ConcurrentHashMap 導(dǎo)致線程不安全

          之前的文章『每天都在用 Map,這些核心技術(shù)你知道嗎?』我們說過 HashMap 是一個(gè)線程不安全的容器,多線程環(huán)境為了線程安全,我們需要使用 ConcurrentHashMap代替。

          但是不要認(rèn)為使用了 ConcurrentHashMap 一定就能保證線程安全,在某些錯(cuò)誤的使用場景下,依然會(huì)造成線程不安全。

          97be6dbbaf4372a33fcd87a3da5e358d.webp

          上面示例代碼,我們原本期望輸出 1001,但是運(yùn)行幾次,得到結(jié)果都是小于 1001

          深入分析這個(gè)問題原因,實(shí)際上是因?yàn)榈谝徊脚c第二步是一個(gè)組合邏輯,不是一個(gè)原子操作。

          ConcurrentHashMap 只能保證這兩步單的操作是個(gè)原子操作,線程安全。但是并不能保證兩個(gè)組合邏輯線程安全,很有可能 A 線程剛通過 get 方法取到值,還未來得及加 1,線程發(fā)生了切換,B 線程也進(jìn)來取到同樣的值。

          這個(gè)問題同樣也發(fā)生在其他線程安全的容器,比如 Vector等。

          上面的問題解決辦法也很簡單,加鎖就可以解決,不過這樣就會(huì)使性能大打折扣,所以不太推薦。

          我們可以使用 AtomicInteger 解決以上的問題。

          9579058287b123c546633450d1db6c72.webp

          List 集合這些坑,Map 中也有

          上一篇文章中我們提過,Arrays#asListList#subList 返回 List 將會(huì)與原集合互相影響,且可能并不支持 add 等方法。同樣的,這些坑爹的特性在 Map 中也存在,一不小心,將會(huì)再次掉坑。

          aaa09162de178d0b2e6c7e074175a9e7.webp

          Map 接口除了支持增刪改查功能以外,還有三個(gè)特有的方法,能返回所有 key,返回所有的 value,返回所有 kv 鍵值對(duì)。

          // 返回 key 的 set 視圖
          Set   keySet ()
          // 返回所有 value   Collection 視圖
          Collection   values ()
          ;
          // 返回 key-value 的 set 視圖
          Set > entrySet();

          這三個(gè)方法創(chuàng)建返回新集合,底層其實(shí)都依賴的原有 Map 中數(shù)據(jù),所以一旦 Map 中元素變動(dòng),就會(huì)同步影響返回的集合。

          另外這三個(gè)方法返回新集合,是不支持的新增以及修改操作的,但是卻支持 clear、remove 等操作。

          示例代碼如下:

          6fd6f85f6a9cf284409bc0894008ca81.webp

          所以如果需要對(duì)外返回 Map 這三個(gè)方法產(chǎn)生的集合,建議再來個(gè)套娃。

          new ArrayList<>(map.values());

          最后再簡單提一下,使用 foreach 方式遍歷新增/刪除 Map 中元素,也將會(huì)和 List 集合一樣,拋出 ConcurrentModificationException

          總結(jié)

          從上面文章可以看到不管是 List 提供的方法返回集合,還是 Map 中方法返回集合,底層實(shí)際還是使用原有集合的元素,這就導(dǎo)致兩者將會(huì)被互相影響。所以如果需要對(duì)外返回,請使用套娃大法,這樣讓別人用的也安心。

          第二, Map 各個(gè)實(shí)現(xiàn)類對(duì)于 null 的約束都不太一樣,這里建議在 Map 中加入元素之前,主動(dòng)進(jìn)行空指針判斷,提前發(fā)現(xiàn)問題。

          第三,慎用自定義對(duì)象作為 Map 中的 key,如果需要使用,一定要重寫 hashCodeequals 方法,并且還要保證這是個(gè)不可變對(duì)象。

          第三,ConcurrentHashMap 是線程安全的容器,但是不要思維定勢,不要片面認(rèn)為使用 ConcurrentHashMap 就會(huì)線程安全。


          各類知識(shí)點(diǎn)總結(jié)

          下面的文章都有對(duì)應(yīng)的原創(chuàng)精美PDF,在持續(xù)更新中,可以來找我催更~

          
           

          掃碼或者微信搜Java3y 免費(fèi)領(lǐng)取原創(chuàng)思維導(dǎo)圖、精美PDF。在公眾號(hào)回復(fù)「888」領(lǐng)取,PDF內(nèi)容純手打有任何不懂歡迎來問我。

          原創(chuàng)電子書
          b61615fd06327a6a2d5778491ac325a8.webp

          原創(chuàng)思維導(dǎo)圖

          9be8b61ed2afadc5f335941829b0f31b.webp


          5309904bdd5695015e329af84ce371ed.webp

          fe4e14d2ab999ab895e087be8294dbb7.webp

          fe4e14d2ab999ab895e087be8294dbb7.webp

          瀏覽 34
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99日韩精品 | 成年免费视频 | 伊人无码不卡电影网 | 天堂俺去俺来也 | 亚洲无码精品九九九 |