什么是重写的System.Object.GetHashCode的最佳算法?

在.NET System.Object.GetHashCode方法中,在.NET基类库中的很多地方都使用了这个方法。 特别是在快速查找集合中的项目或确定平等时。 是否有一个关于如何为我的自定义类实现GetHashCode覆盖的标准算法/最佳实践,所以我不会降低性能?


我通常使用Josh Bloch的神话般的Effective Java中的实现。 它速度很快,创建了一个相当不错的散列,这不太可能导致冲突。 选择两个不同的素数,例如17和23,然后执行:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        // Suitable nullity checks etc, of course :)
        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        hash = hash * 23 + field3.GetHashCode();
        return hash;
    }
}

正如评论中指出的那样,您可能会发现最好选择一个较大的素数来代替。 显然486187739是好的...尽管大多数我看到的小数字的例子倾向于使用素数,但至少有类似的算法使用非素数。 例如,在后来的不太完整的FNV例子中,我使用的数字显然效果不错 - 但初始值不是主要数据。 (虽然乘法常数是主要的,但我不知道这有多重要。)

由于两个主要原因,这比通常的XOR更好。 假设我们有一个包含两个int字段的类型:

XorHash(x, x) == XorHash(y, y) == 0 for all x, y
XorHash(x, y) == XorHash(y, x) for all x, y

顺便说一下,早期的算法是C#编译器当前用于匿名类型的算法。

本页面提供了很多选项。 我认为在大多数情况下,上述内容“足够好”,而且记住和正确的记录非常容易。 FNV的替代方法同样简单,但使用不同的常量和XOR而不是ADD作为组合操作。 它看起来像下面的代码,但正常的FNV算法对单个字节进行操作,所以这需要修改每个字节执行一次迭代,而不是每个32位散列值。 FNV也被设计用于可变长度的数据,而我们在这里使用的方式始终是相同数量的字段值。 对这个答案的评论表明,这里的代码实际上并没有像上面的添加方法那样工作(在样本案例中被测试过)。

// Note: Not quite FNV!
public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = (int) 2166136261;
        // Suitable nullity checks etc, of course :)
        hash = (hash * 16777619) ^ field1.GetHashCode();
        hash = (hash * 16777619) ^ field2.GetHashCode();
        hash = (hash * 16777619) ^ field3.GetHashCode();
        return hash;
    }
}

请注意,有一点需要注意的是,理想情况下,您应该在将其添加到依赖哈希代码的集合后,防止对等同敏感(并因此对哈希码敏感)状态发生更改。

根据文件:

您可以为不可变的引用类型重写GetHashCode。 通常,对于可变引用类型,只有在以下情况下才应该重写GetHashCode:

  • 您可以从不可变的字段计算哈希码; 要么
  • 您可以确保在对象包含在依赖其哈希码的集合中时,可变对象的哈希码不会更改。

  • 微软已经提供了一个很好的通用HashCode生成器:只需将您的属性/字段值复制到匿名类型并对其进行哈希处理即可:

    new { PropA, PropB, PropC, PropD }.GetHashCode();
    

    这将适用于任何数量的属性。 它不使用拳击或额外的资源。 它只是使用匿名类型框架中已经实现的算法。


    这是我的hashcode助手。
    它的优点是它使用泛型类型参数,因此不会导致装箱:

    public static class HashHelper
    {
        public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
        {
             unchecked
             {
                 return 31 * arg1.GetHashCode() + arg2.GetHashCode();
             }
        }
    
        public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
        {
            unchecked
            {
                int hash = arg1.GetHashCode();
                hash = 31 * hash + arg2.GetHashCode();
                return 31 * hash + arg3.GetHashCode();
            }
        }
    
        public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, 
            T4 arg4)
        {
            unchecked
            {
                int hash = arg1.GetHashCode();
                hash = 31 * hash + arg2.GetHashCode();
                hash = 31 * hash + arg3.GetHashCode();
                return 31 * hash + arg4.GetHashCode();
            }
        }
    
        public static int GetHashCode<T>(T[] list)
        {
            unchecked
            {
                int hash = 0;
                foreach (var item in list)
                {
                    hash = 31 * hash + item.GetHashCode();
                }
                return hash;
            }
        }
    
        public static int GetHashCode<T>(IEnumerable<T> list)
        {
            unchecked
            {
                int hash = 0;
                foreach (var item in list)
                {
                    hash = 31 * hash + item.GetHashCode();
                }
                return hash;
            }
        }
    
        /// <summary>
        /// Gets a hashcode for a collection for that the order of items 
        /// does not matter.
        /// So {1, 2, 3} and {3, 2, 1} will get same hash code.
        /// </summary>
        public static int GetHashCodeForOrderNoMatterCollection<T>(
            IEnumerable<T> list)
        {
            unchecked
            {
                int hash = 0;
                int count = 0;
                foreach (var item in list)
                {
                    hash += item.GetHashCode();
                    count++;
                }
                return 31 * hash + count.GetHashCode();
            }
        }
    
        /// <summary>
        /// Alternative way to get a hashcode is to use a fluent 
        /// interface like this:<br />
        /// return 0.CombineHashCode(field1).CombineHashCode(field2).
        ///     CombineHashCode(field3);
        /// </summary>
        public static int CombineHashCode<T>(this int hashCode, T arg)
        {
            unchecked
            {
                return 31 * hashCode + arg.GetHashCode();   
            }
        }
    

    它也有提供流畅接口的扩展方法,所以你可以像这样使用它:

    public override int GetHashCode()
    {
        return HashHelper.GetHashCode(Manufacturer, PartN, Quantity);
    }
    

    或者像这样:

    public override int GetHashCode()
    {
        return 0.CombineHashCode(Manufacturer)
            .CombineHashCode(PartN)
            .CombineHashCode(Quantity);
    }
    
    链接地址: http://www.djcxy.com/p/1029.html

    上一篇: What is the best algorithm for an overridden System.Object.GetHashCode?

    下一篇: When is optimisation premature?