是否可以将私有成员的哈希码组合起来生成新的哈希码?

我有一个对象,我想生成一个唯一的哈希(覆盖GetHashCode()),但我想避免溢出或不可预测的事情。

代码应该是组合一小组字符串的哈希码的结果。

哈希码将是生成缓存密钥的一部分,因此理想情况下它们应该是唯一的,但是被散列的可能值的数量很小所以我认为概率对我有利。

这样的事情是否足够并且有更好的方法吗?

int hash = 0; foreach(string item in collection){ hash += (item.GetHashCode() / collection.Count) } return hash; 

编辑:感谢您的答案到目前为止。 @Jon Skeet:不,订单并不重要

我想这几乎是另一个问题,但由于我使用结果生成缓存键(字符串)是否有意义使用像MD5这样的加密哈希函数或只使用此int的字符串表示?

Marc和Jon指出的基本面并不差,但就结果分布的均匀性而言,它们远非最优。 可悲的是,许多人从Knuth复制的“乘以素数”方法并不是最好的选择,在许多情况下,通过更便宜的计算function可以实现更好的分配(尽管这在现代硬件上非常轻微)。 事实上,将素数投入散列的许多方面并不是灵丹妙药 。

如果这个数据用于大小很大的哈希表,我建议阅读Bret Mulvey对 c#轻松完成的各种现代(而不是那么现代)哈希技术的优秀研究和解释 。

请注意,具有各种散列函数的字符串的行为严重偏向于字符串很短(粗略地说,在位开始溢出之前散列了多少字符)或长。

最简单和最容易实现的一个也是最好的之一,Jenkins One一次哈希。

 private static unsafe void Hash(byte* d, int len, ref uint h) { for (int i = 0; i < len; i++) { h += d[i]; h += (h << 10); h ^= (h >> 6); } } public unsafe static void Hash(ref uint h, string s) { fixed (char* c = s) { byte* b = (byte*)(void*)c; Hash(b, s.Length * 2, ref h); } } public unsafe static int Avalanche(uint h) { h += (h<< 3); h ^= (h>> 11); h += (h<< 15); return *((int*)(void*)&h); } 

你可以这样使用它:

 uint h = 0; foreach(string item in collection) { Hash(ref h, item); } return Avalanche(h); 

您可以合并多个不同类型,如下所示:

 public unsafe static void Hash(ref uint h, int data) { byte* d = (byte*)(void*)&data; AddToHash(d, sizeof(int), ref h); } public unsafe static void Hash(ref uint h, long data) { byte* d= (byte*)(void*)&data; Hash(d, sizeof(long), ref h); } 

如果您只能在不了解内部的情况下访问该字段作为对象,则只需在每个字段上调用GetHashCode()并将其组合如下:

 uint h = 0; foreach(var item in collection) { Hash(ref h, item.GetHashCode()); } return Avalanche(h); 

可悲的是,你不能做sizeof(T)所以你必须单独完成每个结构。

如果您希望使用reflection,您可以在每个类型的基础上构建一个在所有字段上执行结构标识和散列的函数。

如果你想避免使用不安全的代码,那么你可以使用位掩码技术从int中提取单个位(如果处理字符串则为chars),而不需要太多额外的麻烦。

哈希并不是唯一的 – 它们只是意味着在大多数情况下都能很好地分配。 它们只是意味着一致。 请注意,溢出应该不是问题。

只是添加通常不是一个好主意,而分开肯定不是。 这是我通常使用的方法:

 int result = 17; foreach (string item in collection) { result = result * 31 + item.GetHashCode(); } return result; 

如果您处于已检查的上下文中,则可能需要故意将其取消选中。

请注意,这假定顺序很重要,即{“a”,“b”}应与{“b”,“a”}不同。 如果不是这样,请告诉我们。

只要您组合的哈希码遵循哈希码规则的成员,这种方法没有任何问题。 简而言之 …

  1. 私有成员的哈希码不应在对象的生命周期内更改
  2. 容器不得更改私有成员指向的对象,以免它反过来更改容器的哈希代码

如果项目的顺序不重要(即{“a”,“b”}与{“b”,“a”}相同,那么您可以使用exclusive或组合哈希码:

 hash ^= item.GetHashCode(); 

[编辑:正如马克在对不同答案的评论中指出的那样,这样做的缺点是也会给像{“a”}和{“a”,“b”,“b”}这样的集合提供相同的哈希码。

如果订单很重要,您可以改为乘以素数并添加:

 hash *= 11; hash += item.GetHashCode(); 

(当你乘以时,你有时会得到一个被忽略的溢出,但是乘以一个素数就会失去最少的信息。如果你用16乘以一个数字,你每次都会丢失四位信息,所以之后八个项目第一个项目的哈希码将完全消失。)