2011-05-08 53 views
32

我創建了TheKey類型k1 = {17,1375984}和k2 = {17,1593144}的兩種結構。 顯然,第二個字段中的指針是不同的。但是兩者都得到相同的散列碼= 346948941。 預計會看到不同的哈希碼。請參閱下面的代碼。ValueType.GetHashCode的本地實現如何工作?

struct TheKey 
{ 
    public int id; 
    public string Name; 

    public TheKey(int id, string name) 
    { 
     this.id = id; 
     Name = name; 
    } 
} 

static void Main() { 
    // assign two different strings to avoid interning 
    var k1 = new TheKey(17, "abc"); 
    var k2 = new TheKey(17, new string(new[] { 'a', 'b', 'c' })); 

    Dump(k1); // prints the layout of a structure 
    Dump(k2); 

    Console.WriteLine("hash1={0}", k1.GetHashCode()); 
    Console.WriteLine("hash2={0}", k2.GetHashCode()); 
} 

unsafe static void Dump<T>(T s) where T : struct 
{ 
    byte[] b = new byte[8]; 
    fixed (byte* pb = &b[0]) 
    { 
     IntPtr ptr = new IntPtr(pb); 
     Marshal.StructureToPtr(s, ptr, true); 

     int* p1 = (int*)(&pb[0]); // first 32 bits 
     int* p2 = (int*)(&pb[4]); 

     Console.WriteLine("{0}", *p1); 
     Console.WriteLine("{0}", *p2); 
    } 
} 

輸出:
HASH1 = 346948941
HASH2 = 346948941

+0

更多k1.Equals(k2)是真的 – empi 2011-05-08 10:20:22

回答

4

k1和k2包含相同的值。你爲什麼驚訝他們有相同的哈希碼?它與兩個比較相等的對象返回相同的值。

1

哈希碼是根據結構/對象的狀態(值內部)創建的。不是從它的保存位置。根據此:Why is ValueType.GetHashCode() implemented like it is?,值類型GetHashCode的默認行爲struct是,將基於這些值返回散列值。我相信這是結構的特別正確的行爲,它被認爲是不可改變的。

72

它比眼睛複雜得多。對於初學者,給key2值一個完全不同的字符串。注意哈希代碼仍然是相同的:

var k1 = new TheKey(17, "abc"); 
    var k2 = new TheKey(17, "def"); 
    System.Diagnostics.Debug.Assert(k1.GetHashCode() == k2.GetHashCode()); 

這是非常有效的,哈希碼的唯一要求是相同的值產生相同的哈希碼。 不同的值不必產生不同的哈希碼。這在物理上是不可能的,因爲.NET哈希代碼只能代表40億個不同的值。

計算結構的哈希碼是棘手的業務。 CLR所做的第一件事是檢查結構是否包含任何引用類型引用或字段之間存在差距。參考值需要特殊處理,因爲參考值是隨機的。它是一個指針,其值在垃圾收集器壓縮堆時發生變化。由於對齊而創建結構佈局中的空白。具有字節和int的結構在兩個字段之間有3個字節的間隔。

如果不是這種情況,那麼結構值中的所有位都是有意義的。 CLR通過對位進行異或運算來快速計算散列,每次32位。這是一個'好'散列,結構中的所有字段都參與散列碼。

如果結構具有引用類型的字段或有空位,則需要另一種方法。 CLR迭代結構的字段並尋找可用於生成散列的字段。可用的是值類型的字段或非空的對象引用。只要它找到一個,它就會使用該字段的散列值,並將其與方法表指針進行比較,然後退出

換句話說,結構中只有一個字段參與散列碼計算。這是你的情況,只使用id字段。這就是爲什麼字符串成員值無關緊要的原因。

這是一個難以理解的factoid,明顯的重要的是要意識到是否將它留給CLR來爲結構生成哈希碼。到目前爲止,最好的做法是永遠不要這樣做。如果必須,那麼一定要在結構中排序字段,以便第一個字段爲您提供最佳的哈希碼。在你的情況下,只需交換ID名稱字段。


另一個有趣的消息,'好'散列計算代碼有一個錯誤。當結構包含System.Decimal時,它將使用快速算法。問題是,Decimal的位不代表其數值。試試這個:

struct Test { public decimal value; } 

static void Main() { 
    var t1 = new Test() { value = 1.0m }; 
    var t2 = new Test() { value = 1.00m }; 
    if (t1.GetHashCode() != t2.GetHashCode()) 
     Console.WriteLine("gack!"); 
} 
+0

謝謝@Hans。我改變了'TheKey'結構只有Name:string屬性。正如你所說的CLR帶有這個非空字段並從中做出散列。這些哈希值的概率很高(因爲參考值不同)。但他們是平等的...看起來像基類庫(BCL)認識到它是一個字符串字段,並從字符串的字符數組中進行散列。如果我有256個字符的字符串,他們都被哈希?! – tivadj 2011-05-10 18:44:39

+0

是的。使用粘貼箱顯示您嘗試的代碼。 – 2011-05-10 21:14:29