2013-08-03 45 views
15

我在過去的一個小時內讀過很多帖子,但我仍然不清楚使用不可變對象作爲Hashmap中的鍵的概念。我有一個hashmap,它的關鍵字是一個String。散列表中的值是MyStore,其中MyStore表示關於我擁有的商店的信息。該字符串表示地址。在我的代碼中,我擁有的邏輯是,我首先在地圖上查找該鍵(如果存在) - >獲取其值(如果其不存在將其放入散列表中)。我的經理告訴我,未來鑰匙將會改變,那就是我的商店地址將在未來發生變化。他說在那種情況下,我首先檢查密鑰是否存在的邏輯將不起作用。我不明白他在這裏的意思。我想非常清楚地理解以下幾點 -字符串作爲hashmap中的鍵

  1. hashmap的可變鍵和不可變鍵的區別。
  2. 如果使用可更改的不可變密鑰,會發生什麼情況? - 我知道這沒有意義,但我想清楚地理解我的經理在這裏談論的是什麼。
  3. 有些文章談論字符串如果用作hashmap中的鍵緩存他們的哈希碼 - 這是什麼意思?
  4. 如果可以說我使用可變對象作爲實現hashcode和equals的hashmap中的鍵,那麼它會工作嗎?我假設它會因爲如果密鑰更改,包含方法將看看如果密鑰存在。如果它不存在,它會的條目,所以你可以得到它在未來。

我不是說要創建一個重複的帖子,如果這已被討論過。如果我錯過閱讀回答所有問題的帖子,請將它指向我。如果沒有,請用外行解釋我上面提到的問題,這對其他讀者來說是有用的:)。如果任何人有類似的問題,請隨時編輯我的文章的主題,他們直接在這裏登陸:)

+1

這應該回答你的問題 http://stackoverflow.com/questions/214714/mutable-vs-immutable-objects – Spencer

+0

不斷變化的哈希碼打破的HashMap的合同。在這種情況下,您可能需要[按對象引用映射](http://docs.oracle.com/javase/1.5.0/docs/api/java/util/IdentityHashMap.html)。或者將地圖轉換爲對象屬性關係。 –

回答

23

第一:HashMap是如何工作的?

基本上它有一個數組,當你在地圖中放置一個鍵值對時,它被存儲在數組中的一個位置。陣列中的位置是根據傳遞給哈希方法的密鑰的結果hashCode()來選擇的。這是爲什麼?那麼,如果您請求某個鍵的值,則可以簡單地重新計算數組中的索引以查找鍵和其關聯的值,以再次查找數組中的索引。 (需要更多邏輯來處理映射到相同索引的鍵,但我只是試圖讓你理解基本機制)然後equals()用於驗證計算出的索引處的鍵是否確實是所請求的鍵。

  1. 從這應該是更清楚爲什麼不可變鍵優於可變鍵。一個不可變的鍵將始終保持相同的值,並且哈希函數將再次找到正確的桶(= hashMap數組中的索引)。

    這並不意味着可變鍵不起作用。如果密鑰上的突變不會影響哈希碼,或者只要使用哈希映射,那麼密鑰就不會被突變,那麼可變密鑰將會起作用。

  2. 不可變的密鑰怎麼能改變?那麼,密鑰本身可能無法更改,但是鍵值映射可能會在業務邏輯中發生變化。如果您創建地圖,將地址用作關鍵字,則您可以依賴商店地址不會更改的事實。如果商店地址發生變化,您將無法在地圖中使用其新地址作爲關鍵字。你的經理有一個有效的點。

  3. 在Map中查找關鍵字的速度高度依賴於計算hashCode的速度。對於字符串,此計算將循環顯示字符串中的所有字符。如果你使用長字符串作爲鍵和有很多地圖訪問,這可能會導致性能瓶頸。因此Java的String實現會緩存散列值,因此它只會被計算一次。但是,如果您再次使用相同的String實例,則只會避免計算哈希碼(新實例不具有緩存值)。您可能使用的密鑰爲intern(),但只有在可以證明確實存在性能瓶頸的情況下才考慮這一點,因爲String實習會帶來自己的開銷。

  4. 如1所述:如果可變密鑰的哈希碼不受突變的影響,它們可以工作。例如使用客戶作爲密鑰,其中hashCode()僅基於客戶的名稱,那麼客戶實施僅僅不允許名稱更改,但允許其他值更改,這是一個可靠的關鍵。

+0

非常好。謝謝bowmore。 – rickygrimes

+0

字符串的哈希碼已被緩存... – assylias

+0

@assylias編輯我的答案 – bowmore

0

一般來說,hashmaps中的鍵應該是不可變的。

this

注意:如果使用可變對象作爲地圖 按鍵很大,一定要小心。如果對象 的值以影響等於比較的方式進行更改,而 對象是地圖中的關鍵字,則未指定地圖的行爲。

密鑰的散列插入時計算一次,HashMap的存儲,它就會不會得到自動一旦你的項被修改更新。這就是爲什麼有一個假設,關鍵是不可改變的。

你的選擇是: 1.不要使用可變對象作爲鍵。試着找另一個關鍵,或者將以前的關鍵對象 2.不要改變你的可變對象的不變部分,而它們被用作鍵

如果你修改可變對象
+0

您能否回答我上面提到的每個問題?這真的會有所幫助。 – rickygrimes

1
  1. 有可能是一個問題用作鑰匙。即使密鑰存在,map.containsKey(modifiedKey)也可能返回false,您必須遍歷密鑰才能找到它。 因此,儘量使用不可變或不要修改它的可變關鍵。

  2. 不變對象不會改變。有些方法看起來像是在改變對象,而是創建了一個新的副本。例如:

    String a =「A」;

    String b = a.substring(0); // substring創建了一個「A」的副本,並且根本沒有被修改。

    a = a + b; // a + b創建一個新的字符串「AA」,但不修改先前的字符串。

  3. 這可能有助於caching-hashes-in-java-collections也這是偉大的why-are-immutable-objects-in-hashmaps-so-effective

  4. 字符串中有已經實施的equalshashcode,沒有必要去創造,除非你絕對相信你需要另一個類,而不是使用它。

    如第1點所述,您可以這樣做,但您必須小心並且不要修改您的可變對象。但這不是一個很好的做法。

+0

非常感謝你的鏈接 - http://stackoverflow.com/questions/10342859/why-are-immutable-objects-in-hashmaps-so-effective。它回答了我所有的問題。 – rickygrimes

+0

當然可以。但是,我還是不太清楚第二點。另外,請修改您的答案以符合我的要點#4。我指的是不可變對象。 – rickygrimes

1
  1. 不可變的鍵不能改變。因此,在插入時計算的散列碼不能改變。所以當你試圖從地圖得到一個元素時,要獲得的對象的哈希碼是根據已知的哈希碼計算出來的。如果你的密鑰從外部改變(它是可變的),那麼新密鑰的哈希碼將與你插入的哈希碼不同。

  2. 我們來看一個例子。對於(24

    public class RandomPair { 
        int p; 
        int q; 
    
        public RandomPair(int p, int q) { 
         this.p = p; 
         this.q = q; 
        } 
        @Override 
        public int hashCode() { 
         return 31 * p + q; 
        } 
    
        @Override 
        public boolean equals(Object obj) { 
         if (!(obj instanceof RandomPair)) { 
          return false; 
         } 
         if (obj == this) { 
          return true; 
         } 
    
         RandomPair other = (RandomPair) obj; 
         if (p != other.p) 
          return false; 
         if (q != other.q) 
          return false; 
         return true; 
        } 
    
        public static void main(String[] args) { 
         RandomPair pair = new RandomPair(10, 10); 
         Map<RandomPair, Integer> map = new HashMap<RandomPair, Integer>(); 
    
         map.put(pair, 1); 
         System.out.println(map.get(pair)); //returns 1 
    
         //someone somewhere just changed the value of pair 
         pair.p = 20; 
         //the object was the same, someone somewhere just changed value of pair and now you can't 
         //find it in the map 
         System.out.println(map.get(pair)); 
    
         //had you made p and q final, this sort of modification wouldn't be possible 
         //Strings are immutable and thus prevent this modification 
        } 
    } 
    
  3. 由於字符串是不可變,一旦計算出的哈希碼值可被再次重新使用。散列碼是懶散地計算的 。即在第一次調用哈希碼時,然後哈希碼的值被緩存。

+0

謝謝bsd。你還可以添加一些關於我的經理談論的內容嗎?我們以客戶身份接收我們的數據作爲JSON字符串。所以他們肯定會改變地址。我想了解這會如​​何影響我的邏輯? – rickygrimes

+0

不要將地址保存爲計算'hashcode'和'equals'的一部分。常量字段可能是其唯一的10位數字標識號(可能是SSN)應該是'hashcode'的一部分。名稱可能會更改,地址可能會更改。如果您可以找出「Person」對象中發生了什麼變化,您可以從地圖中刪除該項目,並重新插入新的新值。 – bsd

0
  1. 一個可變密鑰或對象意味着可以修改對象[通過修改我的意思是可以改變由對象表示的值]。如果用equals和hashcode編寫的邏輯使用這些可修改的值,這將影響其在HashMap中的存儲。

  2. 理想情況下的不變性意味着一旦初始化後的對象不能在此之後被改變。但是如果我們專門討論HashMap那麼所有在變量equals和hashcode中使用的變量,如果它們可以被修改,那麼該對象不應該被用作關鍵字,否則它可以被用作關鍵字[但仍不推薦]。

  3. 它不只是約String,任何約會緩存其哈希碼。對於幾乎所有的對象,都會一次又一次地生成哈希碼[我之所以這樣說是因爲在某些情況下它可以改變]。 Hashcode緩存在Object頭中。

  4. 如果你想使用可變對象作爲鍵,那麼你應該去IdentityHashMap。只是閱讀它們,它們在這種情況下可能很有用。

相關問題