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


1448

在.NET中,该GetHashCode方法整个.NET基类库中的许多地方都使用。正确实施它对于在集合中或确定相等性时快速查找项目尤为重要。

有没有关于如何GetHashCode为我的自定义类实现的标准算法或最佳实践,因此我不会降低性能?


38
阅读完此问题和下面的文章后,我可以实现对的覆盖GetHashCode。希望对其他人有帮助。埃里克·利珀特(Eric Lippert)撰写的GetHashCode的准则和规则
2012年

4
“还是决定平等”:不!具有相同哈希码的两个对象不一定相等。
Thomas Levesque 2015年

1
@ThomasLevesque是的,具有相同哈希码的两个对象不一定相等。但仍GetHashCode()用于的许多实现中Equals()。这就是我所说的意思。 GetHashCode()inside Equals()通常用作确定不等式的捷径,因为如果两个对象具有不同的哈希码,则它们必须是不相等的对象,并且不必执行其余的相等性检查。
bitbonk 2015年

3
@bitbonk通常情况下,两者GetHashCode()Equals()需要看两个对象的所有字段(equals有了这样做,如果它的哈希码都等于或不选中)。因此,GetHashCode()内部调用Equals()通常是多余的,可能会降低性能。Equals()也许还可以短路,从而使其更快-但是,在某些情况下,哈希码可能会被缓存,从而使GetHashCode()检查更快,因此值得。有关更多信息,请参见此问题
NotEnoughData'4

UPDATE 2020年1月:埃里克利珀的博客地址为:docs.microsoft.com/en-us/archive/blogs/ericlippert/...
里克·达文

Answers:


1602

我通常会使用类似于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:

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

8
您所提到的书中描述的算法实际上更加详细,它特别描述了对字段的不同数据类型执行的操作。例如:对于长时间使用类型的字段,请使用(int)(field ^ f >>> 32)而不是简单地调用GetHashcode。是long.GetHashCodes这样实现的吗?
bitbonk

13
是的,Int64.GetHashCode正是这样做的。在Java中,当然需要装箱。这让我想起了-是时候为该书添加链接了……
乔恩·斯基特

77
23(因为.net 3.5 SP1起)Dictionary<TKey,TValue>假设对某些素数取模良好的分布,因此它不是一个好选择。23是其中之一。因此,如果您有容量为23的字典,则只有最后的贡献会GetHashCode影响复合哈希码。因此,我宁愿使用29而不是
23。– CodesInChaos

23
@CodeInChaos:只有最后一个贡献会影响存储桶-因此,在最坏的情况下,它可能必须浏览字典中的所有23个条目。仍然要检查每个条目的实际哈希码,这很便宜。如果您的字典这么小,那么事情就不太可能了。
乔恩·斯基特

20
@Vajda:我通常使用0作为有效的哈希码null-与忽略字段不同。
乔恩·斯基特

431

匿名类型

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

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

这将适用于任何数量的属性。它不使用拳击。它仅使用框架中已实现的用于匿名类型的算法。

ValueTuple-C#7的更新

正如@cactuaroid在评论中提到的,可以使用值元组。这节省了一些击键,更重要的是,它仅在堆栈上执行(没有垃圾):

(PropA, PropB, PropC, PropD).GetHashCode();

(注意:使用匿名类型的原始技术似乎在堆上创建了一个对象,即垃圾,因为匿名类型是作为类实现的,尽管编译器可能会对其进行优化。对这些选项进行基准测试会很有趣,但是元组选项应该更好。)


85
是的,匿名GetHashCode实现非常有效(顺便说一句,它与Jon Skeet的答案相同),但是此解决方案的唯一问题是,您可以在任何GetHashCode调用时生成一个新实例。特别是在密集访问大型散列集合的情况下,这可能有点开销……
digEmAll 2011年

5
@digEmAll好点,我没有考虑创建新对象的开销。乔恩·斯基特(Jon Skeet)的答案是最有效的,不会使用拳击。(@Kumba要解决VB中未选中的问题,只需使用Int64(长整数)并在计算后将其截断即可。)
Rick Love

42
还是说new { PropA, PropB, PropC, PropD }.GetHashCode()
sehe

17
VB.NET在匿名类型创建中必须使用Key:New With {Key PropA}.GetHashCode()否则,对于具有相同“标识”属性的不同对象,GetHashCode将不会返回相同的哈希码。
大卫·奥斯本

4
@Keith在这种情况下,我会考虑将IEnumerable作为列表值保存在某个地方,而不是每次计算哈希码时都将其枚举。在许多情况下,每次在GetHashCode中计算ToList都会损害性能。
里克·洛夫

105

这是我的哈希码助手。
它的优点是它使用通用类型参数,因此不会引起装箱:

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);
}

5
不需要T[]单独使用,因为它已经是IEnumerable<T>
nawfal

5
您可以重构这些方法并将核心逻辑限制为一个功能
nawfal 2013年

12
顺便说一下,31是CPU上的移位和减法,这是非常快的。
Chui Tey

4
@nightcoder你可以使用params
2015年

6
@ChuiTey这是所有Mersenne Prime的共同点。
法拉普2015年

63

我在Helper库中有一个Hashing类,可用于该目的。

/// <summary> 
/// This is a simple hashing function from Robert Sedgwicks Hashing in C book.
/// Also, some simple optimizations to the algorithm in order to speed up
/// its hashing process have been added. from: www.partow.net
/// </summary>
/// <param name="input">array of objects, parameters combination that you need
/// to get a unique hash code for them</param>
/// <returns>Hash code</returns>
public static int RSHash(params object[] input)
{
    const int b = 378551;
    int a = 63689;
    int hash = 0;

    // If it overflows then just wrap around
    unchecked
    {
        for (int i = 0; i < input.Length; i++)
        {
            if (input[i] != null)
            {
                hash = hash * a + input[i].GetHashCode();
                a = a * b;
            }
        }
    }

    return hash;
}

然后,只需将其用作:

public override int GetHashCode()
{
    return Hashing.RSHash(_field1, _field2, _field3);
}

我没有评估其性能,因此欢迎您提供任何反馈。


26
好吧,如果字段是值类型,它将导致装箱。
nightcoder

5
“可以稍后通过捕获OverflowException进行增强”。的全部要点unchecked是避免在上发生期望的溢出异常GetHashCode。因此,如果值溢出int并且完全没有伤害,那是不正确的。
蒂姆·施密特

1
这种算法的一个问题是,任何充满空值的数组都将始终返回0,无论其长度如何
Nathan Adams

2
此辅助方法还分配了一个新对象[]
James Newton-King

1
正如@NathanAdams提到的那样,null完全跳过的事实可能会给您带来意想不到的结果。而不是跳过它们,您应该只使用一些常量值,而不是input[i].GetHashCode()when input[i]为null。
David Schwartz

58

这是我使用Jon Skeet的实现的帮助程序类。

public static class HashCode
{
    public const int Start = 17;

    public static int Hash<T>(this int hash, T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked((hash * 31) + h);
    }
}

用法:

public override int GetHashCode()
{
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)
        .Hash(_field3);
}

如果要避免编写System.Int32的扩展方法:

public readonly struct HashCode
{
    private readonly int _value;

    public HashCode(int value) => _value = value;

    public static HashCode Start { get; } = new HashCode(17);

    public static implicit operator int(HashCode hash) => hash._value;

    public HashCode Hash<T>(T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked(new HashCode((_value * 31) + h));
    }

    public override int GetHashCode() => _value;
}

它仍然避免任何堆分配,并且使用方式完全相同:

public override int GetHashCode()
{
    // This time `HashCode.Start` is not an `Int32`, it's a `HashCode` instance.
    // And the result is implicitly converted to `Int32`.
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)     
        .Hash(_field3);
}

编辑(2018年5月):EqualityComparer<T>.Defaultgetter现在是JIT内在函数-Stephen Toub在此博客文章中提到了pull请求


1
我将与三级运营商的关系更改为:var h = Equals(obj, default(T)) ? 0 : obj.GetHashCode();
比尔·巴里

我相信,如果是值类型,的三元运算符obj != null将编译为一条box指令,该指令将分配内存T。相反,您可以使用obj.Equals(null)它将编译为该Equals方法的虚拟调用。
Martin Liversage 2014年

因为this.hashCode != h。它不会返回相同的值。
Şafak古尔

抱歉,设法删除我的评论而不是对其进行编辑。创建新的结构,然后将hashCode更改为non-readonly并执行以下操作是否更有益:“ unchecked {th​​is.hashCode ^ = h * 397;}返回this”。例如?
Erik Karlsson

不变性有其好处(为什么可变结构是邪恶的?)。关于性能,我所做的事情非常便宜,因为它不会在堆中分配任何空间。
Şafak古尔

30

.NET Standard 2.1及更高版本

如果使用的是.NET Standard 2.1或更高版本,则可以使用System.HashCode结构。有两种使用方法:

HashCode.Combine

Combine方法可用于创建哈希码,最多提供8个对象。

public override int GetHashCode() => HashCode.Combine(this.object1, this.object2);

HashCode.Add

Add方法可帮助您处理集合:

public override int GetHashCode()
{
    var hashCode = new HashCode();
    hashCode.Add(this.object1);
    foreach (var item in this.collection)
    {
        hashCode.Add(item);
    }
    return hashCode.ToHashCode();
}

GetHashCode变得简单

您可以阅读完整的博客文章“ GetHashCode Made Easy ”,以获取更多详细信息和评论。

使用范例

public class SuperHero
{
    public int Age { get; set; }
    public string Name { get; set; }
    public List<string> Powers { get; set; }

    public override int GetHashCode() =>
        HashCode.Of(this.Name).And(this.Age).AndEach(this.Powers);
}

实作

public struct HashCode : IEquatable<HashCode>
{
    private const int EmptyCollectionPrimeNumber = 19;
    private readonly int value;

    private HashCode(int value) => this.value = value;

    public static implicit operator int(HashCode hashCode) => hashCode.value;

    public static bool operator ==(HashCode left, HashCode right) => left.Equals(right);

    public static bool operator !=(HashCode left, HashCode right) => !(left == right);

    public static HashCode Of<T>(T item) => new HashCode(GetHashCode(item));

    public static HashCode OfEach<T>(IEnumerable<T> items) =>
        items == null ? new HashCode(0) : new HashCode(GetHashCode(items, 0));

    public HashCode And<T>(T item) => 
        new HashCode(CombineHashCodes(this.value, GetHashCode(item)));

    public HashCode AndEach<T>(IEnumerable<T> items)
    {
        if (items == null)
        {
            return new HashCode(this.value);
        }

        return new HashCode(GetHashCode(items, this.value));
    }

    public bool Equals(HashCode other) => this.value.Equals(other.value);

    public override bool Equals(object obj)
    {
        if (obj is HashCode)
        {
            return this.Equals((HashCode)obj);
        }

        return false;
    }

    public override int GetHashCode() => this.value.GetHashCode();

    private static int CombineHashCodes(int h1, int h2)
    {
        unchecked
        {
            // Code copied from System.Tuple a good way to combine hashes.
            return ((h1 << 5) + h1) ^ h2;
        }
    }

    private static int GetHashCode<T>(T item) => item?.GetHashCode() ?? 0;

    private static int GetHashCode<T>(IEnumerable<T> items, int startHashCode)
    {
        var temp = startHashCode;

        var enumerator = items.GetEnumerator();
        if (enumerator.MoveNext())
        {
            temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));

            while (enumerator.MoveNext())
            {
                temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));
            }
        }
        else
        {
            temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber);
        }

        return temp;
    }
}

什么是好的算法?

速度

计算哈希码的算法需要快速。一个简单的算法通常会更快。

确定性

哈希算法必须是确定性的,即,给定相同的输入,它必须始终产生相同的输出。

减少碰撞

计算哈希码的算法需要将哈希冲突保持在最低水平。哈希冲突是当GetHashCode对两个不同对象的两次调用产生相同的哈希码时发生的情况。请注意,冲突是允许的(有些人误解是不允许的),但应将冲突降到最低。

一个好的哈希函数应该在其输出范围内尽可能均匀地映射预期的输入。它应该具有均匀性。

预防的DoS

在.NET Core中,每次重新启动应用程序时,您将获得不同的哈希码。这是一项安全功能,可防止拒绝服务攻击(DoS)。对于.NET Framework,您应该通过添加以下App.config文件启用此功能:

<?xml version ="1.0"?>  
<configuration>  
   <runtime>  
      <UseRandomizedStringHashAlgorithm enabled="1" />  
   </runtime>  
</configuration>

由于此功能,永远不要在创建哈希码的应用程序域之外使用哈希码,永远不要将哈希码用作集合中的关键字段,也永远不能保留它们。

在此处阅读有关此内容的更多信息。

加密安全吗?

该算法不必是加密哈希函数。表示它不必满足以下条件:

  • 生成产生给定哈希值的消息是不可行的
  • 找到两个具有相同哈希值的不同消息是不可行的
  • 对消息的微小更改应会广泛更改哈希值,以使新哈希值看起来与旧哈希值不相关(雪崩效果)。

29

在大多数情况下,Equals()比较多个字段,如果您的GetHash()在一个字段或多个字段上进行哈希处理实际上并不重要。您只需要确保计算哈希值确实便宜(请不要分配)和快速(不需要繁重的计算,当然也没有数据库连接)并且提供了良好的分布。

繁重的工作应该是Equals()方法的一部分;散列应该是一种非常便宜的操作,以允许在尽可能少的项目上调用Equals()。

最后一个提示:不要依赖GetHashCode()在多次复制运行中保持稳定。许多.Net类型不能保证它们的哈希码在重新启动后保持不变,因此您应该仅将GetHashCode()的值用于内存数据结构中。


10
“在大多数情况下,Equals()比较多个字段,如果您的GetHash()散列在一个或多个字段上并没有关系。” 这是危险的建议,因为对于仅在未哈希字段中不同的对象,您将获得哈希冲突。如果这种情况经常发生,则基于哈希的集合(HashMap,HashSet等)的性能将下降(在最坏的情况下可达O(n))。
sleske 2010年

10
这实际上是在Java中发生的:在JDK的早期版本中,String.hashCode()仅考虑字符串的开头。如果您在HashMaps中使用Strings作为键,而这仅在结尾处有所不同(例如,对于URL来说是常见的),则会导致性能问题。因此更改了算法(我相信在JDK 1.2或1.3中)。
sleske 2010年

3
如果一个字段“提供了良好的分布”(我的回答的最后一部分),那么一个字段就足够了。如果它没有提供良好的分布,那么(就在那时)您需要进行另一次计算。(例如,仅使用确实提供良好分布的另一个字段,或使用多个字段)
Bert Huijben 2010年

我认为GetHashCode执行内存分配没有问题,只要它在第一次使用时就这样做(随后的调用只是返回缓存的结果)。重要的不是不是要竭尽全力避免冲突,而是应该避免“系统性”冲突。如果一个类型有两个int字段oldX并且newX经常相差一个字段,则哈希值oldX^newX会为此类记录分配90%的哈希值,即1、2、4或8。使用oldX+newX[unchecked算术]可能会产生更多冲突...
supercat

1
...比更复杂的功能要好,但是如果每个哈希值具有两个关联的事物,则具有100万个具有500,000个不同哈希值的事物的集合将非常好,如果一个哈希值具有500,001个事物而另一个哈希值具有一个事物,则非常糟糕。
2013年

23

直到最近,我的回答与Jon Skeet的回答非常接近。但是,我最近开始了一个项目,该项目使用2的幂的哈希表,即内部表的大小为8、16、32等的哈希表。有充分的理由偏爱素数大小,但是同样也是2的幂数的一些优势。

它几乎吸了。因此,经过一些实验和研究,我开始使用以下方法重新哈希散列:

public static int ReHash(int source)
{
  unchecked
  {
    ulong c = 0xDEADBEEFDEADBEEF + (ulong)source;
    ulong d = 0xE2ADBEEFDEADBEEF ^ c;
    ulong a = d += c = c << 15 | c >> -15;
    ulong b = a += d = d << 52 | d >> -52;
    c ^= b += a = a << 26 | a >> -26;
    d ^= c += b = b << 51 | b >> -51;
    a ^= d += c = c << 28 | c >> -28;
    b ^= a += d = d << 9 | d >> -9;
    c ^= b += a = a << 47 | a >> -47;
    d ^= c += b << 54 | b >> -54;
    a ^= d += c << 32 | c >> 32;
    a += d << 25 | d >> -25;
    return (int)(a >> 1);
  }
}

然后我的2的幂的哈希表不再吸引人。

但是,这使我不安,因为上述方法不起作用。或更确切地说,除非原件GetHashCode()以某种特殊方式变差,否则它不起作用。

重新混合哈希码不能改善出色的哈希码,因为唯一可能的效果是我们引入了更多冲突。

重新混合哈希码不能改善可怕的哈希码,因为唯一可能的效果是我们将例如值53上的大量冲突更改为值18,3487,291的大量。

重新混合哈希码只能改善一个哈希码,该哈希码至少在避免整个范围内的绝对冲突(2 32个可能的值)方面做得相当好,但是在为哈希表中的实际使用取模时避免避免冲突很差。虽然2的幂的表的简单模数使这一点更加明显,但它对更常见的素数表也有负面影响,但效果却不那么明显(重新哈希处理的额外工作将超过收益) ,但好处仍然存在)。

编辑:我也在使用开放式寻址,这也将增加对碰撞的敏感度,可能比事实为二乘幂的情况还要多。

而且,令人不安的是string.GetHashCode().NET(或在此处学习)的实现方式可以通过这种方式得到多少改进(由于冲突较少,测试运行速度提高了约20-30倍),并且更多地干扰了我自己的哈希码可以改进(远远不止于此)。

我过去编写的所有GetHashCode()实现(实际上已用作该站点的答案的基础)都比我经历的要差得多。在很多时候,它对于许多用途来说都“足够好”,但是我想要更好的东西。

因此,我将该项目放在一边(无论如何,这都是一个宠物项目),然后开始研究如何在.NET中快速生成良好的,分布良好的哈希代码。

最后,我决定将SpookyHash移植到.NET。实际上,上面的代码是使用SpookyHash从32位输入生成32位输出的快速路径版本。

现在,SpookyHash并不是一种快速记住代码的好方法。我的端口号甚至更少,因为我手工插入了很多端口以提高速度*。但这就是代码重用的目的。

然后,我将该项目放在一边,因为就像原始项目产生了如何产生更好的哈希码的问题一样,所以那个项目也产生了如何产生更好的.NET memcpy的问题。

然后我回来了,并产生了很多重载,可以轻松地将几乎所有本机类型(decimal† 除外)都输入到哈希码中。

很快,Bob Jenkins值得赞扬,因为我移植的原始代码仍然更快,尤其是在针对该算法进行了优化的64位计算机上。

完整的代码可以在https://bitbucket.org/JonHanna/spookilysharp/src上找到,但是请认为上面的代码是它的简化版本。

但是,由于它已经被编写,因此可以更轻松地使用它:

public override int GetHashCode()
{
  var hash = new SpookyHash();
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

它还需要种子值,因此,如果您需要处理不受信任的输入并想要防止Hash DoS攻击,则可以基于正常运行时间或类似时间设置种子,并使攻击者无法预测结果:

private static long hashSeed0 = Environment.TickCount;
private static long hashSeed1 = DateTime.Now.Ticks;
public override int GetHashCode()
{
  //produce different hashes ever time this application is restarted
  //but remain consistent in each run, so attackers have a harder time
  //DoSing the hash tables.
  var hash = new SpookyHash(hashSeed0, hashSeed1);
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

*令人惊讶的是,手动插入旋转方法可以返回(x << n) | (x >> -n)改进的结果。我本来可以确定,抖动对我而言是内联的,但分析显示并非如此。

decimal虽然来自C#,但从.NET角度来看并不是原生的。它的问题在于,它自己GetHashCode()将精度视为重要,而自己Equals()却不那么重视。两者都是有效的选择,但并非如此。在实现自己的版本时,您需要选择一个或另一个,但是我不知道您想要哪个。

‡通过比较的方式。如果用于字符串,则64位SpookyHash的速度比string.GetHashCode() 32位上的速度快得多string.GetHashCode(),后者比64位上的速度要快一些,后者比32位上的SpookyHash速度要快得多,尽管仍然足够快以至于是一个合理的选择。


当将多个散列值组合为一个时,我倾向于long将中间结果使用值,然后将最终结果减为int。这似乎是个好主意吗?我担心的是,例如使用hash =(hash * 31)+ nextField,那么成对的匹配值将仅影响哈希的高27位。让计算扩展到a long并包装东西将使危险最小化。
supercat 2014年

@supercat,这取决于您最终的想法分布。SpookilySharp库将确保传递良好,理想情况下(因为它不需要创建对象)是通过传递指向blittable类型的指针,或者传递直接处理的可枚举值之一,但是如果您还没有blittable数据或适当的枚举,然后.Update()按照上述答案使用多个值调用即可。
乔恩·汉纳

@JonHanna您愿意对遇到的问题行为更精确吗?我正在尝试实现一个库,该库使实现值对象变得不那么容易(ValueUtils),并且我很喜欢一个测试集,该测试集证明了在2幂次哈希表中较差的哈希兼容性。
Eamon Nerbonne 2014年

@EamonNerbonne我真的没有比“总的时间更慢”更精确的了。正如我在编辑中所添加的那样,我使用开放地址这一事实可能比二乘幂因素更为重要。我确实打算在一个特定的项目上做一些测试用例,在这里我将比较几种不同的方法,因此在那之后我可能会为您提供一个更好的答案,尽管这不是一个高优先级(一个个人项目,没有紧迫的需求,所以我一到便会解决...)
乔恩·汉纳

@JonHanna:是的,我知道个人项目的进度如何-祝你好运!无论如何,我发现我对最后一个评论的措辞不好:我是要征求有问题的意见,而不一定是所导致问题的细节。我很乐意将其用作测试集(或测试集的灵感)。无论如何-祝您的宠物项目好运:-)。
Eamon Nerbonne 2014年

13

这个不错:

/// <summary>
/// Helper class for generating hash codes suitable 
/// for use in hashing algorithms and data structures like a hash table. 
/// </summary>
public static class HashCodeHelper
{
    private static int GetHashCodeInternal(int key1, int key2)
    {
        unchecked
        {
           var num = 0x7e53a269;
           num = (-1521134295 * num) + key1;
           num += (num << 10);
           num ^= (num >> 6);

           num = ((-1521134295 * num) + key2);
           num += (num << 10);
           num ^= (num >> 6);

           return num;
        }
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="arr">An array of objects used for generating the 
    /// hash code.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode(params object[] arr)
    {
        int hash = 0;
        foreach (var item in arr)
            hash = GetHashCodeInternal(hash, item.GetHashCode());
        return hash;
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <param name="obj4">The fourth object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and
    /// data structures like a hash table.
    /// </returns>
    public static int GetHashCode<T1, T2, T3, T4>(T1 obj1, T2 obj2, T3 obj3,
        T4 obj4)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3, obj4));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2, T3>(T1 obj1, T2 obj2, T3 obj3)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2>(T1 obj1, T2 obj2)
    {
        return GetHashCodeInternal(obj1.GetHashCode(), obj2.GetHashCode());
    }
}

这是如何使用它:

private struct Key
{
    private Type _type;
    private string _field;

    public Type Type { get { return _type; } }
    public string Field { get { return _field; } }

    public Key(Type type, string field)
    {
        _type = type;
        _field = field;
    }

    public override int GetHashCode()
    {
        return HashCodeHelper.GetHashCode(_field, _type);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Key))
            return false;
        var tf = (Key)obj;
        return tf._field.Equals(_field) && tf._type.Equals(_type);
    }
}

1
如何确定钥匙?GetHashCode()不接受任何参数,因此需要使用需要以某种方式确定的两个键来调用该参数。抱歉,没有进一步的解释,这看起来很聪明,但还不够好。
Michael Stum

为什么需要通用重载?因为所有对象都有一个GetHashCode()方法,所以类型并不重要(并且在您的代码中未使用),因此您始终可以将该方法与paramsarray参数一起使用。还是我在这里想念什么?
gehho 2010年

4
当使用对象而不是泛型时,将获得装箱和内存分配,而这在GetHashCode中是不需要的。因此,泛型是必经之路。
CodesInChaos

1
尾随的shift / xor步骤(h += (h << 10); h ^= (h >> 6); h += (h << 3); h ^= (h >> 11); h += (h << 15);有一个代码提示:它们不依赖于任何输入,对我来说看起来很多余。)
sehe 2011年

1
@Magnus是的,我将删除原始评论。请注意,这可能不如此处的其他解决方案快,但正如您所说的那样。分布很好,比这里的大多数解决方案都要好,所以我+1!:)
nawfal

11

https://github.com/dotnet/coreclr/pull/14863开始,有一种新的生成哈希码的方法非常简单!写就好了

public override int GetHashCode()
    => HashCode.Combine(field1, field2, field3);

这将生成高质量的哈希码,而您无需担心实现细节。


这看起来像是一个很好的补充……任何方式都可以知道将发行哪个版本的.NET Core?
Dan J

1
@DanJ巧合的是,HashCode对corefx所做的更改仅在您发表评论前几个小时就被合并了:)该类型预定在.NET Core 2.1中发布。
詹姆斯·科(James Ko)

太棒了-而且还需要很多时间。已投票。:)
Dan J

@DanJ甚至更好的消息-它应该现在可以在以点网为核心的MyGet feed上托管的CoreFX的夜间版本中获得。
詹姆斯·柯

甜-这并不能帮助我在工作中,因为我们还没有那个流血边缘,但很好的了解。干杯!
Dan J

9

这是Jon Skeet在上面发布的算法的另一种流畅的实现,但是不包括分配或装箱操作:

public static class Hash
{
    public const int Base = 17;

    public static int HashObject(this int hash, object obj)
    {
        unchecked { return hash * 23 + (obj == null ? 0 : obj.GetHashCode()); }
    }

    public static int HashValue<T>(this int hash, T value)
        where T : struct
    {
        unchecked { return hash * 23 + value.GetHashCode(); }
    }
}

用法:

public class MyType<T>
{
    public string Name { get; set; }

    public string Description { get; set; }

    public int Value { get; set; }

    public IEnumerable<T> Children { get; set; }

    public override int GetHashCode()
    {
        return Hash.Base
            .HashObject(this.Name)
            .HashObject(this.Description)
            .HashValue(this.Value)
            .HashObject(this.Children);
    }
}

HashValue由于通用类型约束,编译器将确保不使用类进行调用。但是没有编译器支持,HashObject因为添加通用参数还会添加装箱操作。


8

这是我的简单化方法。我正在为此使用经典的构建器模式。它是类型安全的(无装箱/拆箱),并且与.NET 2.0兼容(无扩展方法等)。

它的用法如下:

public override int GetHashCode()
{
    HashBuilder b = new HashBuilder();
    b.AddItems(this.member1, this.member2, this.member3);
    return b.Result;
} 

这是自动生成器类:

internal class HashBuilder
{
    private const int Prime1 = 17;
    private const int Prime2 = 23;
    private int result = Prime1;

    public HashBuilder()
    {
    }

    public HashBuilder(int startHash)
    {
        this.result = startHash;
    }

    public int Result
    {
        get
        {
            return this.result;
        }
    }

    public void AddItem<T>(T item)
    {
        unchecked
        {
            this.result = this.result * Prime2 + item.GetHashCode();
        }
    }

    public void AddItems<T1, T2>(T1 item1, T2 item2)
    {
        this.AddItem(item1);
        this.AddItem(item2);
    }

    public void AddItems<T1, T2, T3>(T1 item1, T2 item2, T3 item3)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
    }

    public void AddItems<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, 
        T4 item4)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
    }

    public void AddItems<T1, T2, T3, T4, T5>(T1 item1, T2 item2, T3 item3, 
        T4 item4, T5 item5)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
        this.AddItem(item5);
    }        

    public void AddItems<T>(params T[] items)
    {
        foreach (T item in items)
        {
            this.AddItem(item);
        }
    }
}

您可以避免像在Mangus的答案中那样在gethashcode函数内部创建对象。只需调用该死的静态哈希函数(他们关心入门哈希)。同样,您可以AddItems<T>(params T[] items)在助手类中更频繁地使用method(而不是AddItem(T)每次调用)。
nawfal 2013年

您发现this.result * Prime2 * item.GetHashCode()经常使用时有什么好处this.result * Prime2 + item.GetHashCode()
nawfal 2013年

我无法用AddItems<T>(params T[] items)更多的时候是因为typeof(T1) != typeof(T2)
bitbonk

哦,是的,我错过了。
nawfal

5

ReSharper用户可以使用生成GetHashCode,等于等ReSharper -> Edit -> Generate Code -> Equality Members

// ReSharper's GetHashCode looks like this
public override int GetHashCode() {
    unchecked {
        int hashCode = Id;
        hashCode = (hashCode * 397) ^ IntMember;
        hashCode = (hashCode * 397) ^ OtherIntMember;
        hashCode = (hashCode * 397) ^ (RefMember != null ? RefMember.GetHashCode() : 0);
        // ...
        return hashCode;
    }
}

4

如果我们不超过8个属性(希望如此),这是另一种选择。

ValueTuple 是一个结构,似乎有一个坚实的 GetHashCode实现。

这意味着我们可以简单地做到这一点:

// Yay, no allocations and no custom implementations!
public override int GetHashCode() => (this.PropA, this.PropB).GetHashCode();

让我们来看看.NET核心的当前实现ValueTupleGetHashCode

这是从ValueTuple

    internal static int CombineHashCodes(int h1, int h2)
    {
        return HashHelpers.Combine(HashHelpers.Combine(HashHelpers.RandomSeed, h1), h2);
    }

    internal static int CombineHashCodes(int h1, int h2, int h3)
    {
        return HashHelpers.Combine(CombineHashCodes(h1, h2), h3);
    }

这是从HashHelper

    public static readonly int RandomSeed = Guid.NewGuid().GetHashCode();

    public static int Combine(int h1, int h2)
    {
        unchecked
        {
            // RyuJIT optimizes this to use the ROL instruction
            // Related GitHub pull request: dotnet/coreclr#1830
            uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
            return ((int)rol5 + h1) ^ h2;
        }
    }

用英语:

  • 向左旋转(循环移位)h1 5个位置。
  • 将结果和h1相加。
  • 将结果与h2进行XOR。
  • 首先对{静态随机种子,h1}执行上述操作。
  • 对于每个其他项目,请对上一个结果和下一个项目(例如h2)执行操作。

很高兴了解更多有关此ROL-5哈希码算法的属性。

遗憾的是,推迟到ValueTuple我们自己那里GetHashCode可能不会像我们期望和期望的那样快。在相关讨论中,此评论说明直接调用HashHelpers.Combine的性能更高。另一方面,该代码是内部的,因此我们必须复制代码,以牺牲我们在此处获得的很多东西。另外,我们将负责记住首先Combine使用随机种子。我不知道如果跳过这一步会带来什么后果。


假设h1 >> 27忽略它的值为0,因此h1 << 5等于。根据此页面,它称为“修改后的伯恩斯坦”。h1 * 32h1 * 33 ^ h2
cactuaroid

3

我的大部分工作都是通过数据库连接完成的,这意味着我的所有班级都有来自数据库的唯一标识符。我总是使用数据库中的ID生成哈希码。

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}

这意味着,如果您有对象“人”和“帐户”,并且它们都具有ID = 1,则它们将具有相同的哈希码。那不行。
pero

15
其实上面的评论是不正确的。始终存在哈希码冲突的可能性(哈希码仅定位存储桶,而不是单个对象)。因此,对于包含混合对象的哈希码,这样的实现会导致很多冲突,这是不希望的,但是如果您在哈希表中仅拥有单一类型的对象,那将是绝对好的。它也分布不均匀,但是
system.object

2
哈希码可以只是id,因为id是整数。无需在整数上调用GetHashCode(这是一个标识函数)
Darrel Lee

2
@DarrelLee,但是他的_id可能是Guid。_id.GetHashCode因为意图很明确,所以这是一个很好的编码实践。
nawfal 2013年

2
@ 1224根据使用模式,由于您给出的原因,这可能会很可怕,但也可能很棒;如果您有一个无孔的数字序列,那么您将拥有完美的哈希值,这比任何算法都可以产生的更好。如果您知道这种情况,您甚至可以依靠它并跳过相等性检查。
乔恩·汉纳

3

与nightcoder的解决方案非常相似,不同之处在于,如果需要,可以更容易地增加素数。

PS:这是您向嘴巴吐一点口的情况之一,因为您知道可以使用9个默认值将其重构为一种方法,但是它会比较慢,因此您只需闭上眼睛,然后就去忘掉它。

/// <summary>
/// Try not to look at the source code. It works. Just rely on it.
/// </summary>
public static class HashHelper
{
    private const int PrimeOne = 17;
    private const int PrimeTwo = 23;

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();
            hash = hash * PrimeTwo + arg10.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();

            return hash;
        }
    }
}

2
不处理空值。
JJS

1

使用上面作为答案选择的实现,我遇到了浮点数和小数的问题。

该测试失败(浮点数;即使我将2的值切换为负数,哈希也相同):

        var obj1 = new { A = 100m, B = 100m, C = 100m, D = 100m};
        var obj2 = new { A = 100m, B = 100m, C = -100m, D = -100m};
        var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D);
        var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D);
        Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different   hash1:{0}  hash2:{1}",hash1,hash2));

但是此测试通过(带有整数):

        var obj1 = new { A = 100m, B = 100m, C = 100, D = 100};
        var obj2 = new { A = 100m, B = 100m, C = -100, D = -100};
        var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D);
        var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D);
        Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different   hash1:{0}  hash2:{1}",hash1,hash2));

我将实现更改为不对原始类型使用GetHashCode,并且看起来效果更好

    private static int InternalComputeHash(params object[] obj)
    {
        unchecked
        {
            var result = (int)SEED_VALUE_PRIME;
            for (uint i = 0; i < obj.Length; i++)
            {
                var currval = result;
                var nextval = DetermineNextValue(obj[i]);
                result = (result * MULTIPLIER_VALUE_PRIME) + nextval;

            }
            return result;
        }
    }



    private static int DetermineNextValue(object value)
    {
        unchecked
        {

                int hashCode;
                if (value is short
                    || value is int
                    || value is byte
                    || value is sbyte
                    || value is uint
                    || value is ushort
                    || value is ulong
                    || value is long
                    || value is float
                    || value is double
                    || value is decimal)
                {
                    return Convert.ToInt32(value);
                }
                else
                {
                    return value != null ? value.GetHashCode() : 0;
                }
        }
    }

1
如果您原本打算unchecked不影响Convert.ToInt32uint,,longfloatdouble并且decimal可能在此处全部溢出。
马克·赫德2014年

1

微软领先的几种哈希方法...

//for classes that contain a single int value
return this.value;

//for classes that contain multiple int value
return x ^ y;

//for classes that contain single number bigger than int    
return ((int)value ^ (int)(value >> 32)); 

//for classes that contain class instance fields which inherit from object
return obj1.GetHashCode();

//for classes that contain multiple class instance fields which inherit from object
return obj1.GetHashCode() ^ obj2.GetHashCode() ^ obj3.GetHashCode(); 

我可以猜测,对于多个big int,您可以使用以下代码:

int a=((int)value1 ^ (int)(value1 >> 32));
int b=((int)value2 ^ (int)(value2 >> 32));
int c=((int)value3 ^ (int)(value3 >> 32));
return a ^ b ^ c;

与多类型相同:首先将所有转换为int使用,GetHashCode() 然后将int值进行异或运算,结果是您的哈希。

对于那些使用哈希作为ID(我是一个唯一值)的人,哈希自然地被限制为多个数字,对于哈希算法,我认为它是5个字节,至少是MD5。

您可以将多个值转换为散列值,其中一些是相同的,因此请勿将其用作标识符。(也许有一天我将使用您的组件)


7
将整数异或为哈希码是一种众所周知的反模式,它往往导致与实际值的冲突特别大。
乔恩·汉娜

这里的每个人都使用整数,并且从来没有任何形式可以保证哈希值是相同的,它只是尽可能地使变化尽可能多,而不会发生冲突。
deadManN

是的,但是您的第二个和第五个不要试图避免碰撞。
乔恩·汉娜

1
是的,这种反模式非常普遍。
乔恩·汉娜

2
有一个平衡点。使用像Spookyhash这样的非常好的哈希代码,您将获得更好得多的冲突避免,但与任何这些方法相比,它的计算时间要长得多(但是在对大量数据进行哈希处理时,Spookyhash的速度非常快)。在异或之前对值之一进行简单转换只是边际额外成本,可以很好地减少碰撞。质数乘法再次提高了时间和质量。因此在轮班或多人之间哪个更好是值得商de的。普通xor通常会在真实数据上发生很多冲突,因此最好避免
Jon Hanna 2015年

1

这是一个静态助手类,用于实现Josh Bloch的实现。并提供显式的重载以“防止”装箱,并特别为长原语实现哈希。

您可以传递与equals实现匹配的字符串比较。

由于哈希输出始终是一个整数,因此您可以只链接哈希调用。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;


namespace Sc.Util.System
{
    /// <summary>
    /// Static methods that allow easy implementation of hashCode. Example usage:
    /// <code>
    /// public override int GetHashCode()
    ///     => HashCodeHelper.Seed
    ///         .Hash(primitiveField)
    ///         .Hsh(objectField)
    ///         .Hash(iEnumerableField);
    /// </code>
    /// </summary>
    public static class HashCodeHelper
    {
        /// <summary>
        /// An initial value for a hashCode, to which is added contributions from fields.
        /// Using a non-zero value decreases collisions of hashCode values.
        /// </summary>
        public const int Seed = 23;

        private const int oddPrimeNumber = 37;


        /// <summary>
        /// Rotates the seed against a prime number.
        /// </summary>
        /// <param name="aSeed">The hash's first term.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int rotateFirstTerm(int aSeed)
        {
            unchecked {
                return HashCodeHelper.oddPrimeNumber * aSeed;
            }
        }


        /// <summary>
        /// Contributes a boolean to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aBoolean">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, bool aBoolean)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + (aBoolean
                                ? 1
                                : 0);
            }
        }

        /// <summary>
        /// Contributes a char to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aChar">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, char aChar)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + aChar;
            }
        }

        /// <summary>
        /// Contributes an int to the developing HashCode seed.
        /// Note that byte and short are handled by this method, through implicit conversion.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aInt">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, int aInt)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + aInt;
            }
        }

        /// <summary>
        /// Contributes a long to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aLong">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, long aLong)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + (int)(aLong ^ (aLong >> 32));
            }
        }

        /// <summary>
        /// Contributes a float to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aFloat">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, float aFloat)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + Convert.ToInt32(aFloat);
            }
        }

        /// <summary>
        /// Contributes a double to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aDouble">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, double aDouble)
            => aSeed.Hash(Convert.ToInt64(aDouble));

        /// <summary>
        /// Contributes a string to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aString">The value to contribute.</param>
        /// <param name="stringComparison">Optional comparison that creates the hash.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(
                this int aSeed,
                string aString,
                StringComparison stringComparison = StringComparison.Ordinal)
        {
            if (aString == null)
                return aSeed.Hash(0);
            switch (stringComparison) {
                case StringComparison.CurrentCulture :
                    return StringComparer.CurrentCulture.GetHashCode(aString);
                case StringComparison.CurrentCultureIgnoreCase :
                    return StringComparer.CurrentCultureIgnoreCase.GetHashCode(aString);
                case StringComparison.InvariantCulture :
                    return StringComparer.InvariantCulture.GetHashCode(aString);
                case StringComparison.InvariantCultureIgnoreCase :
                    return StringComparer.InvariantCultureIgnoreCase.GetHashCode(aString);
                case StringComparison.OrdinalIgnoreCase :
                    return StringComparer.OrdinalIgnoreCase.GetHashCode(aString);
                default :
                    return StringComparer.Ordinal.GetHashCode(aString);
            }
        }

        /// <summary>
        /// Contributes a possibly-null array to the developing HashCode seed.
        /// Each element may be a primitive, a reference, or a possibly-null array.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aArray">CAN be null.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, IEnumerable aArray)
        {
            if (aArray == null)
                return aSeed.Hash(0);
            int countPlusOne = 1; // So it differs from null
            foreach (object item in aArray) {
                ++countPlusOne;
                if (item is IEnumerable arrayItem) {
                    if (!object.ReferenceEquals(aArray, arrayItem))
                        aSeed = aSeed.Hash(arrayItem); // recursive call!
                } else
                    aSeed = aSeed.Hash(item);
            }
            return aSeed.Hash(countPlusOne);
        }

        /// <summary>
        /// Contributes a possibly-null array to the developing HashCode seed.
        /// You must provide the hash function for each element.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aArray">CAN be null.</param>
        /// <param name="hashElement">Required: yields the hash for each element
        /// in <paramref name="aArray"/>.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash<T>(this int aSeed, IEnumerable<T> aArray, Func<T, int> hashElement)
        {
            if (aArray == null)
                return aSeed.Hash(0);
            int countPlusOne = 1; // So it differs from null
            foreach (T item in aArray) {
                ++countPlusOne;
                aSeed = aSeed.Hash(hashElement(item));
            }
            return aSeed.Hash(countPlusOne);
        }

        /// <summary>
        /// Contributes a possibly-null object to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aObject">CAN be null.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, object aObject)
        {
            switch (aObject) {
                case null :
                    return aSeed.Hash(0);
                case bool b :
                    return aSeed.Hash(b);
                case char c :
                    return aSeed.Hash(c);
                case int i :
                    return aSeed.Hash(i);
                case long l :
                    return aSeed.Hash(l);
                case float f :
                    return aSeed.Hash(f);
                case double d :
                    return aSeed.Hash(d);
                case string s :
                    return aSeed.Hash(s);
                case IEnumerable iEnumerable :
                    return aSeed.Hash(iEnumerable);
            }
            return aSeed.Hash(aObject.GetHashCode());
        }


        /// <summary>
        /// This utility method uses reflection to iterate all specified properties that are readable
        /// on the given object, excluding any property names given in the params arguments, and
        /// generates a hashcode.
        /// </summary>
        /// <param name="aSeed">The developing hash code, or the seed: if you have no seed, use
        /// the <see cref="Seed"/>.</param>
        /// <param name="aObject">CAN be null.</param>
        /// <param name="propertySelector"><see cref="BindingFlags"/> to select the properties to hash.</param>
        /// <param name="ignorePropertyNames">Optional.</param>
        /// <returns>A hash from the properties contributed to <c>aSeed</c>.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashAllProperties(
                this int aSeed,
                object aObject,
                BindingFlags propertySelector
                        = BindingFlags.Instance
                        | BindingFlags.Public
                        | BindingFlags.GetProperty,
                params string[] ignorePropertyNames)
        {
            if (aObject == null)
                return aSeed.Hash(0);
            if ((ignorePropertyNames != null)
                    && (ignorePropertyNames.Length != 0)) {
                foreach (PropertyInfo propertyInfo in aObject.GetType()
                        .GetProperties(propertySelector)) {
                    if (!propertyInfo.CanRead
                            || (Array.IndexOf(ignorePropertyNames, propertyInfo.Name) >= 0))
                        continue;
                    aSeed = aSeed.Hash(propertyInfo.GetValue(aObject));
                }
            } else {
                foreach (PropertyInfo propertyInfo in aObject.GetType()
                        .GetProperties(propertySelector)) {
                    if (propertyInfo.CanRead)
                        aSeed = aSeed.Hash(propertyInfo.GetValue(aObject));
                }
            }
            return aSeed;
        }


        /// <summary>
        /// NOTICE: this method is provided to contribute a <see cref="KeyValuePair{TKey,TValue}"/> to
        /// the developing HashCode seed; by hashing the key and the value independently. HOWEVER,
        /// this method has a different name since it will not be automatically invoked by
        /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>,
        /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless
        /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise
        /// the generated hash code will not be consistent. This method itself ALSO will not invoke
        /// this method on the Key or Value here if that itself is a KeyValuePair.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="keyValuePair">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashKeyAndValue<TKey, TValue>(this int aSeed, KeyValuePair<TKey, TValue> keyValuePair)
            => aSeed.Hash(keyValuePair.Key)
                    .Hash(keyValuePair.Value);

        /// <summary>
        /// NOTICE: this method is provided to contribute a collection of <see cref="KeyValuePair{TKey,TValue}"/>
        /// to the developing HashCode seed; by hashing the key and the value independently. HOWEVER,
        /// this method has a different name since it will not be automatically invoked by
        /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>,
        /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless
        /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise
        /// the generated hash code will not be consistent. This method itself ALSO will not invoke
        /// this method on a Key or Value here if that itself is a KeyValuePair or an Enumerable of
        /// KeyValuePair.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="keyValuePairs">The values to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashKeysAndValues<TKey, TValue>(
                this int aSeed,
                IEnumerable<KeyValuePair<TKey, TValue>> keyValuePairs)
        {
            if (keyValuePairs == null)
                return aSeed.Hash(null);
            foreach (KeyValuePair<TKey, TValue> keyValuePair in keyValuePairs) {
                aSeed = aSeed.HashKeyAndValue(keyValuePair);
            }
            return aSeed;
        }
    }
}

Yipes:我发现了一个错误!该HashKeysAndValues方法已修复:调用HashKeyAndValue
史蒂文·可可

0

如果您想HashCodenetstandard2.1

public static class HashCode
{
    public static int Combine(params object[] instances)
    {
        int hash = 17;

        foreach (var i in instances)
        {
            hash = unchecked((hash * 31) + (i?.GetHashCode() ?? 0));
        }

        return hash;
    }
}

注意:如果与一起使用struct,它将由于装箱而分配内存

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.