簡體   English   中英

為什么更改用作HashMap中的鍵的對象的哈希碼會使查找返回null?

[英]Why does changing the hashcode of an object used as a key in a HashMap make a lookup return null?

請考慮以下情形:

Object o1 = new Object();
Object o2 = new Object();

HashMap<Object, Object> map = new HashMap<Object, Object>();
map.put(o1, o2);

boolean test1 = map.get(o1) == o2; // This evaluates to true

// Now lets say we alter the state of o1:
o1.setSomeInternalState(Object newState);

boolean test2 = map.get(o1) == o2; // This evaluates to false, because now map.get(o1) returns null

假設o1的類已重寫equals()hashCode()

我在調試期間遇到過這個問題,因為我在一些業務邏輯中使用的一個特定對象上顯式地重寫了equalshashCode 我完全理解為什么當我改變它的狀態時對象的哈希碼會改變,但是為什么map.get(o1)會因為它而返回null? 只有一個對象,所以密鑰的哈希碼不應該匹配嗎?

HashMap類通過哈希函數運行密鑰的hashCode ,將鍵映射到值。 哈希函數用於創建桶數組的索引。 例如, 非常原始的哈希函數是hashCode % tableSize 更改密鑰的hashCode會改變散列函數創建的索引,這意味着在該存儲桶中找不到任何內容。

讓我們運行一個例子,假設初始hashCode是15並且表大小是4:

                         ┌----------------------┐
15 (initial hashCode) -> | hashCode % tableSize | -> index 3
                         |    (hash function)   |
                         └----------------------┘

所以讓我們在索引3處插入值:

  ┌------┐
0 | null |
  |------|
1 | null |
  |------|
2 | null |
  |------|
3 | key! | <- insert
  └------┘

現在讓我們修改密鑰的hashCode ,使其現在為13:

                          ┌----------------------┐
13 (modified hashCode) -> | hashCode % tableSize | -> index 1
                          |    (hash function)   |
                          └----------------------┘

索引1是什么? 沒有, null

這里簡化了很多東西。 在實際的哈希表實現中,哈希函數要復雜得多,以創建更均勻的分布。 此外,存儲桶是鏈接列表,因此可以處理沖突。

您已經使用一個hashCode存儲它,並使用另一個更改的hashCode查找它,因此您的程序的行為符合預期。 這就是為什么HashMap的合同具體說明你不應該使用hashCodes可以改變的鍵。 如果我是你,我會遵循這個建議。

哈希碼用於存儲對象,因此可以查找它 如果在存儲對象后更改哈希碼,則查找失敗的可能性很大。

實現細節可能不同,但基本上基於散列的集合包含一組對象桶。 哈希碼指示對象存儲在基於哈希的集合中的哪個存儲桶( equals()方法然后標識該存儲桶中的對象 - 如果您的集合被正確縮放,那么將只有一個這樣的對象)。 當您的哈希碼更改時,您的查找很可能會在集合中找到不同的項目桶,因此您的對象似乎丟失了。

出於這個原因,建議從對象的不可變字段創建哈希碼。

請注意,您可以更改哈希碼,並可能仍然找到您的對象。 您的哈希碼是一個整數(一個32位數字),並且通常會映射到更小的一組桶(例如通過某種計算,例如hashcode % 16 )。 因此,您的哈希碼可能會發生變化,但hashcode % 16的結果可能會產生相同的結果,因此也會產生相同的桶。 顯然,這取決於實現。

map.get搜索其哈希碼與要查找的對象相同的對象。 由於這兩個對象具有不同的哈希碼,因此它將返回null,認為此對象不在地圖中

final Entry<K,V> getEntry(Object key) {
    int hash = (key == null) ? 0 : hash(key);
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}

如果沒有對象具有hash stehash == hash,則返回null

這就是你必須定義的hashCode方法。 所以,假設我們有兩個字段的員工類:

class Employee {
    int id; 
    String name;
    public int hashCode() { 
        return name.hashCode() ^ id;
    }
}

現在,如果你只是設置名稱,你最終可能會得到名稱的哈希碼(並且默認id為0,這將返回名稱的hashCode),而如果我以后將id更改為1甚至1,那么它可能會創建另一個hashCode xoring名稱的哈希碼為1。

雖然hashCode()的契約經常用實現者來描述,但從調用者的角度考慮它有時更有用: 知道兩個對象返回不同值的代碼, hashCode()有權假設他們不可能是平等的 雖然哈希的許多描述都談論了桶指數,但哈希的問題超出了這個范圍。

基本上, hashCode()的目的是使快速識別大量不可能與項目相等的東西成為可能。 雖然將事物細分為哈希碼符合各種標准的桶是很常見的(哈希代碼符合某些標准的一堆事物不能包含哈希碼不符合該標准的任何東西),但這並不是哈希碼的唯一用途。 如果集合類在添加項時記錄項的哈希碼,則可以通過首先檢查它們是否包含相同的哈希值序列來檢查兩個實例是否包含相同的項序列。 如果哈希值全部匹配,那么將需要單獨檢查項目,但是如果例如每個集合的第五十項的哈希值不同,則沒有理由詳細檢查前49項。

根據上面的粗體表述及其使用和合同含義來考慮哈希碼將比在桶中考慮它更清楚。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM