什么是重写的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?