2011-04-07 72 views
12

我一直在沉思這一段時間:在CLR或Java中究竟是如何實現Object.GetHashCode?該方法的契約是,如果它在同一個對象實例上被調用,它應該總是返回相同的值。如何在CLR和JVM中實現Object.GetHashCode()?

請注意,我在談論GetHashCode()的默認實現。派生類不需要重寫此方法。如果他們選擇不這樣做,他們實質上將具有引用語義:在哈希表中使用時,默認情況下,默認情況下「默認等於」指針相等「& c。這意味着,運行時不得不在整個生命週期中爲對象提供一個恆定的哈希碼。

如果我運行的機器是32位的,並且如果對象實例從未在內存中移動,理論上可以返回對象的地址,重新解釋爲Int32。這很好,因爲所有不同的對象都有不同的地址,因此會有不同的哈希碼。

但是,這種方法是有缺陷的,除其他事情,因爲:

  • 如果垃圾收集器在內存中移動對象時,它的地址變化,所以會違反合同的散列碼的哈希碼在對象的生命週期中應該相同。

  • 在64位系統上,對象的地址太寬,無法放入Int32。

  • 由於被管理對象傾向於與某些偶數次冪對齊,所以最低位將始終爲零。當散列碼用於索引散列表時,這可能會導致分佈模式不正確。

在.NET中,一個System.Object由同步塊和型手柄,僅此而已,所以哈希碼不能在該實例本身緩存。不知何故,運行時能夠提供持久的哈希碼。怎麼樣? Java,Mono和其他運行時如何做到這一點?

回答

9

不,不是地址,不能用垃圾回收器移動對象。它直觀簡單,只要它在生成後存儲,它就可以是一個隨機數。它確實得到存儲在對象,syncblk。該字段存儲多個對象屬性,如果需要存儲多個這樣的屬性,則將其替換爲分配的syncblk的索引。

在.NET算法使用託管線程ID,以便線程可能不會產生相同的序列:

inline DWORD GetNewHashCode() 
{ 
    // Every thread has its own generator for hash codes so that we won't get into a situation 
    // where two threads consistently give out the same hash codes.   
    // Choice of multiplier guarantees period of 2**32 - see Knuth Vol 2 p16 (3.2.1.2 Theorem A) 
    DWORD multiplier = m_ThreadId*4 + 5; 
    m_dwHashCodeSeed = m_dwHashCodeSeed*multiplier + 1; 
    return m_dwHashCodeSeed; 
} 

種子被存儲每線程因此不需要鎖。至少這是SSCLI20版本中使用的。不知道Java,我想它是相似的。

+0

謝謝你的回答,這很有道理。我的想法被鎖定在想法上,即同步塊只能存儲單個東西,但是您在此處繪製的機制解釋瞭如何將多個額外屬性按需添加到對象。 – 2011-04-07 14:07:49

0

我不確定你的意思是「在CLR或Java中如何實現Object.GetHashCode?」。 Java的「public int hashCode()」具有合約,即類的作者應該爲其定義hashCode()實現。換句話說,它可能在不同班級之間有很大差異。我懷疑這對於.Net平臺也是如此。

爲對象的Javadoc描述了一種類似於你的想法的方法: http://download.oracle.com/javase/1.4.2/docs/api/java/lang/Object.html#hashCode()

儘可能多是合理可行的, 按類 對象定義的hashCode方法不會返回不同的整數 針對不同的對象。 (這是 通常通過轉換 對象 的內部地址轉換成一個整數來實現,但這種 實現技術是不被的JavaTM編程 語言所需 。)

這種方法是不恰當的,如果你已經定義了你的班級的平等是基於身份以外的東西。

+2

當你從Object類派生時,你不需要實現GetHashCode。通過不這樣做,你已經實現了引用語義。上面的JavaDoc意味着「通常」,對象(以及任何派生類不超越其實現的GetHashCode)的哈希碼將返回對象的地址。如果GC移動你的對象,你現在將擁有與GC之前相同的對象的不同哈希碼。這與Hashtables不能很好地發揮,我猜測。 – 2011-04-07 13:51:16

+0

你是對的,我沒有要求,這就是爲什麼我把「應該」放在第二句話中,而不是「必須」。 =)我仍然認爲這是一個很好的做法,就像Trent Gray-Donald在他的回答中所說的那樣。 – Jolta 2011-04-12 08:37:18

4

作爲一個JVM實現者,我可以說基礎哈希碼IS通常與對象的地址有關。這通常不是正確的地址,但有一些以合理的方式改變了地址。我們通過魔術來確保hashCode在對象的整個生命週期中保持穩定(甚至在GC上也是如此,即使對象移動等)。

我強烈建議爲所有對象實現一個好的類型特定的hashCode將會哈希。該對象實現它並不意味着它是您使用的理想選擇。

+1

魔術。說真的,我並不是想回避,但它有時是競爭優勢的來源。我可以說的是,我們的JVM的某些版本在我們頭文件的「哈希和標誌」部分保留了一些位。這通常不是完整的32位散列,所以使用了一些重複(並導致潛在熵的損失)。其他選項包括記住一個對象是否被散列,因此如果對象需要移動,則記住「原始」散列。它可能保存在某種脫機數據結構中,或者可能保存在對象的另一部分中。 – 2011-04-09 19:29:08