所以,JaredPar的回答并不糟糕,但有几个方面可以改进。首先,IEqualityComparer页面中指出,“我们建议您从EqualityComparer类派生而来,而不是实现IEqualityComparer接口。”
其次,GetHashCode的实现应该尽可能地快速。它用于快速消除显然不同的对象,在这些对象上运行Equals显然是浪费时间的。因此,GetHashCode应该比实际运行Equals要快得多。
第三,像JaredPar所做的返回字节数组的总和非常容易产生冲突 - 如果字节顺序不同,或者相对差异互相抵消等。
因此,我建议采用以下解决方案:
public class ByteArrayComparer : EqualityComparer<byte[]>
{
public override bool Equals(byte[] first, byte[] second)
{
if (first == null || second == null) {
return first == second;
}
if (ReferenceEquals(first, second)) {
return true;
}
if (first.Length != second.Length) {
return false;
}
return first.SequenceEqual(second);
}
public override int GetHashCode(byte[] obj)
{
if (obj == null) {
throw new ArgumentNullException("obj");
}
return obj.Length;
}
}
以上,返回obj.Length确实很快也很简单粗暴,但也容易出现很多碰撞(即哈希冲突)。我认为我们可以做得更好。
如果你要检查所有字节,像JaredPar的答案中那样简单地将所有字节相加会产生较少的冲突。但同样地,这需要检查所有元素,所以它并不比实际运行Equals更优。你可能最好无条件地返回0,并始终强制使用Equals。
我强调:这比像JaredPar答案中返回总和要好。而总是返回0比这个更好。而返回obj.Length比返回0更好。
public override int GetHashCode(byte[] obj)
{
if (obj == null) {
throw new ArgumentNullException("obj");
}
int sum = 0;
int sumOfSum = 0;
foreach (var val in obj) {
sum += val;
sumOfSum += sum;
}
return sum ^ sumOfSum;
}
如果您知道用作密钥的 byte[] 数组本身是加密哈希值,则可以利用此假设,简单地返回前四个字节转换为 int
。这对于通用的 byte[] 数组也可能有效:
public override int GetHashCode(byte[] obj)
{
if (obj == null) {
throw new ArgumentNullException("obj");
}
if (obj.Length >= 4) {
return BitConverter.ToInt32(obj, 0);
}
int value = obj.Length;
foreach (var b in obj) {
value <<= 8;
value += b;
}
return value;
}