我們都知道Map的一大特性是key唯一不可重復,可是真的是這樣的嗎?
我們來試驗一下:
運行結果:
我們可以看到在map里有兩個同樣的person作為key,打破了map的key不可重復的特性。
我們平時操作map一般不會出現這樣的結果,怎樣操作會出現上述的現象呢?
1、首先有前提條件,作為key的person必須重寫hashCode與equals這兩個方法保證我們在改變person的屬性之后,該person的hash值發生變化。
2、其次是我們在map中put一個以person對象作為key的元素,然后我們修改該person對象的某一個屬性,再次把該person對象作為key值put到map中,就得到了上述結果。
為什么會出現上述的問題呢?
我們要明白map的數據結構以及數據是如何存儲到map中的
JDK1.7 HashMap是數組+鏈表的結構
JDK8之后HashMap是數組+鏈表+紅黑樹的結構
tips:當然這里我們就不過多的討論7與8結構的區別
我們在put一個元素的時候,(其余邏輯省略,這里只關注元素如何定位到數組的桶位置),先拿到key對象的hash值h1,h1無符號右移16位得到h2,
再把h1與h2進行異或運算得到h3,h3與數組的(length-1)進行與運算得到元素在數組上的最終位置
tips:如果數組長度較小的時候(大多數情況下map的長度不大),key產生的hash值如果高位變化較大很大,而地位變化很小時,
如果直接拿key的hash值與上(length-1)很容易產生hash沖突,所以無符號右移16位在異或低16位使得高混亂區域與低混亂區域做一個中和,提高hash高低位的一個隨機性,減少hash沖突
上面我們講述了map是如何把元素放入到數組中的,我們再回到上面的問題,第一次把person作為key放入map之后,修改了person的name屬性之后,person的hash值發生變化,從而計算出的
桶位置也隨之而改變(大概率會改變,不是絕對的)再次put到map中就得到兩個相同key值的map。
那么在生產應用中我們要避免使用類似於person這樣的對象作為key值存儲在Map中,可以使用Integer、String這樣一些不可變的對象來作為key就可以避免上述情況的發生。
插一句題外話,HashSet是無序不可重復的,它其實也存在上面的情況,原因很簡單HashSet的底層就是HashMap
附HashSet相關代碼截圖