簡體   English   中英

Object.GetHashCode

[英]Object.GetHashCode

我的問題可能會重復Object.GetHashCode()的默認實現,但我再問一次,因為我不理解那個問題的接受答案。

首先,我對上一個問題的接受答案有三個問題,引用了一些文檔如下:

“但是,因為在垃圾回收期間回收對象后可以重用此索引,所以可以為兩個不同的對象獲取相同的哈希碼。”

這是真的? 在我看來,兩個對象將不具有相同的哈希碼,因為在對象被垃圾收集(即不再存在)之前不會重用對象的代碼。

“另外,表示相同值的兩個對象只有在完全相同的對象時才具有相同的哈希碼。”

這是一個問題嗎? 例如,我想將一些數據與DOM樹中的每個節點實例相關聯。 為此,“節點”必須具有標識或哈希碼,以便我可以將它們用作數據字典中的鍵。 是不是一個哈希碼來識別它是否是“完全相同的對象”,即“引用相等而不是”值相等“,我想要什么?

“這個實現對於散列並不是特別有用;因此,派生類應該覆蓋GetHashCode”

這是真的? 如果它對散列不好,那么如果它有什么好處,為什么它甚至被定義為Object的方法呢?


我的最終(也許對我來說最重要的)問題是,如果我必須發明/覆蓋具有“引用相等”語義的任意類型的GetHashCode()實現,則以下是一個合理且良好的實現:

class SomeType
{
  //create a new value for each instance
  static int s_allocated = 0;
  //value associated with this instance
  int m_allocated;
  //more instance data
  ... plus other data members ...
  //constructor
  SomeType()
  {
    allocated = ++s_allocated;
  }
  //override GetHashCode
  public override int GetHashCode()
  {
    return m_allocated;
  }
}

編輯

僅供參考我使用以下代碼測試它:

    class TestGetHash
    {
        //default implementation
        class First
        {
            int m_x;
        }
        //my implementation
        class Second
        {
            static int s_allocated = 0;
            int m_allocated;
            int m_x;
            public Second()
            {
                m_allocated = ++s_allocated;
            }
            public override int GetHashCode()
            {
                return m_allocated;
            }
        }
        //stupid worst-case implementation
        class Third
        {
            int m_x;
            public override int GetHashCode()
            {
                return 0;
            }
        }

        internal static void test()
        {
            testT<First>(100, 1000);
            testT<First>(1000, 100);
            testT<Second>(100, 1000);
            testT<Second>(1000, 100);
            testT<Third>(100, 100);
            testT<Third>(1000, 10);
        }

        static void testT<T>(int objects, int iterations)
            where T : new()
        {
            System.Diagnostics.Stopwatch stopWatch =
                System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < iterations; ++i)
            {
                Dictionary<T, object> dictionary = new Dictionary<T, object>();
                for (int j = 0; j < objects; ++j)
                {
                    T t = new T();
                    dictionary.Add(t, null);
                }
                for (int k = 0; k < 100; ++k)
                {
                    foreach (T t in dictionary.Keys)
                    {
                        object o = dictionary[t];
                    }
                }
            }
            stopWatch.Stop();
            string stopwatchMessage = string.Format(
                "Stopwatch: {0} type, {1} objects, {2} iterations, {3} msec",
                typeof(T).Name, objects, iterations,
                stopWatch.ElapsedMilliseconds);
            System.Console.WriteLine(stopwatchMessage);
        }
    }

在我的機器上,結果/輸出如下:

First type, 100 objects, 1000 iterations, 2072 msec
First type, 1000 objects, 100 iterations, 2098 msec
Second type, 100 objects, 1000 iterations, 1300 msec
Second type, 1000 objects, 100 iterations, 1319 msec
Third type, 100 objects, 100 iterations, 1487 msec
Third type, 1000 objects, 10 iterations, 13754 msec

我的實現占用了默認實現的一半時間(但是我的類型因我的m_allocated數據成員的大小而變大)。

我的實現和默認實現都是線性擴展的。

相比之下,作為一個健全性檢查,愚蠢的實施開始變壞並且變得更糟。

哈希代碼實現必須具有的最重要的屬性是:

如果兩個對象相等,那么它們必須具有相同的哈希碼。

如果你有一個類,通過引用相等比較類的實例,那么你不需要重寫GetHashCode; 默認實現保證兩個具有相同引用的對象具有相同的哈希代碼。 (你在同一個對象上調用兩次相同的方法,所以當然結果是一樣的。)

如果您編寫了一個實現與引用相等不同的自身相等的類,那么您需要重寫GetHashCode,這樣兩個比較相等的對象具有相同的哈希碼。

現在,您可以通過每次返回零來實現。 這將是一個糟糕的哈希函數,但它是合法的。

良好散列函數的其他屬性是:

  • GetHashCode永遠不應該拋出異常

  • 可變對象比較其可變狀態的相等性,因此在其可變狀態上進行哈希處理,這些對象很容易出錯。 您可以將對象放入哈希表中,對其進行修改,並且無法再將其刪除。 嘗試永遠不要散列或比較可變狀態的相等性。

  • GetHashCode應該非常快 - 請記住,良好的哈希算法的目的是提高查找的性能。 如果哈希很慢,則無法快速查找。

  • 不相等的對象應具有不同的哈希碼,在32位整數的整個范圍內均勻分布

題:

這是真的? 在我看來,兩個對象將不具有相同的哈希碼,因為在對象被垃圾收集(即不再存在)之前不會重用對象的代碼。

如果默認的GetHashCode實現生成,則兩個對象可以共享相同的哈希代碼,因為:

  1. 在對象的生命周期內不應更改默認的GetHashCode結果 ,默認實現可確保這一點。 如果它可以改變,像Hashtable這樣的類型無法處理這種實現。 那是因為預期默認哈希碼是唯一實例標識符的哈希碼(即使沒有這樣的標識符:))。
  2. GetHashCode值的范圍是整數范圍(2 ^ 32)。

結論: 足夠分配2 ^ 32個強引用對象(必須在Win64上很容易)才能達到極限。

最后, MSDN中的object.GetHashCode引用中有一個顯式語句:GetHashCode方法的默認實現不保證不同對象的唯一返回值。 此外,.NET Framework不保證GetHashCode方法的默認實現,並且它返回的值在不同版本的.NET Framework之間是相同的。 因此,此方法的默認實現不得用作散列目的的唯一對象標識符。

實際上,您不需要修改只需要引用相等的類的任何內容。

而且,正式地說,這不是一個好的實施,因為它的分配很差。 散列函數應該具有合理的分布,因為它改進了散列桶分布,並且間接地改善了使用散列表的集合中的性能。 正如我所說,這是一個正式的答案,是設計哈希函數時的指導原則之一。

暫無
暫無

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

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