Object.GetHashCode

时间:2009-07-16 19:28:09

标签: c# .net hash gethashcode

我的问题可能会重复Default implementation for Object.GetHashCode()但我会再次提问,因为我不理解该答案的接受答案。

首先,我有三个关于accepted answer to the previous question的问题,引用some documentation如下:

  

“但是,因为在垃圾回收期间回收对象后可以重用此索引,所以可以为两个不同的对象获取相同的哈希码。”

这是真的吗?在我看来,两个对象将不具有相同的哈希码,因为在对象被垃圾收集(即不再存在)之前,对象的代码不会被重用。

  

“另外,表示相同值的两个对象只有在完全相同的对象时才具有相同的哈希码。”

这是一个问题吗?例如,我想将一些数据与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数据成员的大小而变大)。

我的实现和默认实现都是线性扩展。

相比之下,作为一个健全性检查,愚蠢的实施开始变坏并且变得更糟。

3 个答案:

答案 0 :(得分:36)

哈希代码实现必须具备的最重要的属性是:

如果两个对象相等,那么它们必须具有相同的哈希码。

如果你有一个类,通过引用相等比较类的实例,那么你不需要重写GetHashCode;默认实现保证两个具有相同引用的对象具有相同的哈希代码。 (你在同一个对象上调用两次相同的方法,所以当然结果是一样的。)

如果你编写了一个实现与引用相等不同的自身相等的类,则需要覆盖GetHashCode,这样两个比较相等的对象具有相同的哈希码。

现在,你可以通过简单地每次返回零来实现。这将是一个糟糕的哈希函数,但它是合法的。

良好散列函数的其他属性是:

  • GetHashCode不应该抛出异常

  • 可变对象比较其可变状态的相等性,因此在其可变状态上进行哈希处理,是危险的容易出错的对象。您可以将对象放入哈希表中,对其进行修改,并且无法再将其删除。尽量不要哈希或比较可变状态的相等性。

  • GetHashCode应该非常快 - 请记住,良好的哈希算法的目的是提高查找的性能。如果哈希很慢,则无法快速查找。

  • 不相等的对象应该有不同的哈希码,分布在32位整数的整个范围内

答案 1 :(得分:2)

问题:

  

这是真的吗?在我看来,两个对象将没有相同的哈希码,因为   在对象被垃圾收集(即不再存在)之前,不会重复使用对象的代码。

两个对象可以共享相同的哈希码,如果它是由默认的GetHashCode实现生成的,因为:

  1. 在对象的生命周期内不应更改默认的GetHashCode结果,默认实现可确保这一点。如果它可以改变,像Hashtable这样的类型无法处理这种实现。那是因为预计默认哈希码是唯一实例标识符的哈希码(即使没有这样的标识符:))。
  2. GetHashCode值的范围是整数范围(2 ^ 32)。
  3. <强>结论: 足以分配2 ^ 32个强引用对象(在Win64上必须很容易)才能达到极限。

    最后,object.GetHashCode reference in MSDN中有一个显式语句:GetHashCode方法的默认实现不保证不同对象的唯一返回值。此外,.NET Framework不保证GetHashCode方法的默认实现,并且它返回的值在不同版本的.NET Framework之间是相同的。因此,不得将此方法的默认实现用作散列目的的唯一对象标识符。

答案 2 :(得分:0)

您实际上不需要修改只需要引用相等的类的任何内容。

另外,正式地说,由于分发不佳,这不是一个好的实施。散列函数应该具有合理的分布,因为它改进了散列桶分布,并且间接地改善了使用散列表的集合中的性能。正如我所说,这是一个正式的答案,是设计哈希函数时的指导原则之一。