2017-08-31 86 views
0

在項目中,我使用的HashMap爲了存儲一些數據,以及最近我發現,當我的突變HashMap的鍵上,一些意想不到的錯誤的結果可能發生。例如:突變導致錯誤的結果

HashMap<ArrayList,Integer> a = new HashMap<>(); 
ArrayList list1 = new ArrayList<>(); 
a.put(list1, 1); 
System.out.println(a.containsKey(new ArrayList<>())); // true 
list1.add(5); 
ArrayList list2 = new ArrayList<>(); 
list2.add(5); 
System.out.println(a.containsKey(list2)); // false 

注意兩個a.keySet().iterator().next().hashCode() == list2.hashCode()a.keySet().iterator().next().equals(list2)是真實的。

我不明白爲什麼會發生,指的是事實,兩個對象是相等的,並具有相同的哈希碼。有誰知道這是什麼原因,並且如果有其他類似的結構可以讓鑰匙變形?謝謝。

+4

不要使用可變值hashmap鍵。我認爲它在HashMap的文檔中說過,但目前我找不到它。 –

+0

我知道我不應該(關於我已經展示的例子),但我不明白原因(對象是平等的,並具有相同的哈希代碼),我正在尋找一個類似的結構,允許。 – RanSch

+1

「......如果有其他類似的結構可以讓鑰匙變形?」 - 鑰匙(至少是形成鑰匙的部件)從來不會變得可變,因爲那樣會違反它們的使用。從現實世界的角度來看它:如果你改變你的鑰匙,它不會再打開鎖。如果你改變鎖定,你需要一個新的密鑰。 – Thomas

回答

3

可變鍵總是一個問題。如果突變可能改變它們的散列碼和/或equals()的結果,則鍵被認爲是可變的。也就是說,列表通常會生成他們的哈希碼並根據它們的元素檢查相等性,所以它們幾乎從來都不是地圖關鍵字的好選擇。

你的例子有什麼問題?當添加密鑰時,它是一個空列表,從而產生與包含元素時不同的哈希碼。因此,即使密鑰和list2的哈希碼與更改密鑰列表後相同,您也不會找到該元素。爲什麼?只是因爲地圖看起來不對勁。

實施例(簡化):

讓我們先從一些假設:

  • 空列表,如果該列表包含它返回哈希碼元件5返回的0
  • 的哈希碼5
  • 我們的地圖具有16個桶
  • 剷鬥索引由散列碼%16(我們的桶的數量)
  • 確定(默認)

如果你現在它被插入到桶0空單增加,由於其哈希碼。

當你做list1查找它看起來鬥5由於5哈希碼自那桶是空的什麼都不會被發現。

的問題是,你的密鑰列表改變其哈希碼,因此應該被置於不同的水桶,但地圖並不知道發生這種情況(以及這樣做可能會導致一堆其他問題)。

+0

非常感謝,我現在可以理解它。 – RanSch

-2

也許你沒有重載equals()和hashCode()方法。

+0

它們已實施。 – RanSch

1

這是因爲HashMap使用hashCode()對象方法結合equals(Object obj)來檢查此映射是否包含對象。

參見:

ArrayList<Integer> a = new ArrayList<>(); 
a.add(1); 
System.out.println(a.hashCode()); 
a.add(2); 
System.out.println(a.hashCode()); 

這個例子顯示,你的ArrayList的hashCode發生了變化。

+0

啊,我明白了。謝謝。你知道是否有類似的數據結構允許關鍵突變? – RanSch

+0

@RanSch也許['IdentityHashMap'](http://docs.oracle.com/javase/7/docs/api/java/util/IdentityHashMap.html)適合您? – Thomas

+0

@Thomas不,因爲我需要「等於」檢查,但謝謝。 – RanSch

3

根據的Javadoc Map

注意:如果使用可變對象作爲地圖 按鍵很大,一定要小心。如果對象 的值以影響等於比較的方式進行更改,而 對象是地圖中的關鍵字,則未指定地圖的行爲。這種禁令的一個特例是 地圖不允許自己作爲關鍵字。儘管 對於包含自身值的地圖是允許的,但建議您謹慎使用 :在這樣的地圖上,不再定義equals和hashCode方法 。

你的清單是鑰匙,你正在改變它們。如果列表中的內容不是,那麼這將不會成爲問題什麼決定哈希碼的值和什麼是平等的,但這不是你的情況。如果你仔細想想,改變地圖的關鍵是沒有什麼意義的。關鍵在於識別價值,如果該密鑰更改,所有投注都關閉。

該映射在插入時插入給定散列碼的值。當您稍後搜索它時,它會使用參數的哈希碼來確定它是否是命中。我想你會發現,如果你插入的list1值已經插入,你會看到「true」被打印出來,因爲list2.hashCode()在插入時會產生與list1相同的哈希碼。

0

你不應該在你的散列表中使用一個可變對象作爲鍵。

那麼,當你把list1作爲第三行中的鍵時,基本上會發生什麼,就是map計算它的hashCode,它將在稍後在containsKey(someKey)中進行比較。 但是當u突變第5行中的list1時,其hashCode實質上發生了改變。 所以如果u現在要做的

System.out.println(a.containsKey(list1)); 

5號線後,會說false 和如果u做System.out.println(a.get(list1));

它會說null作爲其比較兩個不同的散列碼