Marc 和 Jon 指出的基本原理还不错,但就结果分布的均匀性而言,它们远非最佳。可悲的是,许多人从 Knuth 复制的“乘以素数”方法并不是最佳选择,在许多情况下,可以通过更便宜的计算函数来实现更好的分布(尽管这在现代硬件上非常轻微)。事实上,将素数放入散列的许多方面并不是灵丹妙药。
如果此数据用于显着大小的哈希表,我建议阅读Bret Mulvey对使用 c# 轻松完成的各种现代(而不是那么现代)散列技术的出色研究和解释。
请注意,各种散列函数的字符串的行为严重偏向于字符串是短的(粗略地说,在位开始溢出之前散列了多少个字符)或长。
最简单和最容易实现的一种也是最好的一种,Jenkins One at a time hash。
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) 所以你必须单独做每个结构。
如果您希望使用反射,您可以基于每个类型构造一个函数,该函数对所有字段进行结构标识和散列。
如果您希望避免不安全的代码,那么您可以使用位掩码技术从整数(如果处理字符串则为字符)中提取单个位,而不会带来太多额外的麻烦。