同一个对象,特别是string
或任何原始或非常简单的类型(如struct
)是否可以在调用时生成.GetHashCode()
方法的不同值在不同的机器上?
例如,表达式"Hello World".GetHashCode()
是否可以在不同的机器上生成不同的值。我主要是要求C#.NET,但我想这可能适用于Java甚至其他语言?
编辑:
从下面的答案和评论中可以看出,我知道.GetHashCode()
可以覆盖,并且无法保证它在不同版本的框架之间产生的结果。因此,重要的是要澄清我有简单的类型(不能继承,因此GetHashCode()
被覆盖)并且我在所有机器上使用相同版本的框架。
答案 0 :(得分:14)
简短回答:是的。
但简短的回答并不好玩,是吗?
当您实施GetHashCode()
时,您必须做出以下保证:
如果在另一个应被视为等于此对象的对象上调用
GetHashCode()
,则在此App Domain中将返回相同的值。
就是这样。有一些事情你真的需要尝试做(尽可能多地使用不相等的对象传播它们,但不要花太多时间,它首先超过散列的所有好处)和你的代码如果你不这样做会很糟糕,但它实际上不会破裂。如果你没有那么远,它会破裂,因为那样,例如:
dict[myObj] = 3;
int x = dict[myObj];//KeyNotFoundException
好。如果我正在实施GetHashCode()
,为什么我会更进一步,为什么不呢?
首先,我为什么不呢?
也许这是一个略有不同的程序集版本,我在构建之间进行了改进(或至少尝试过)。
也许一个是32位,一个是64位,我为了提高效率而疯狂,为每个选择不同的算法来使用不同的字大小(这并不是闻所未闻,特别是在散列像集合这样的对象时或字符串)。
在决定构成“平等”对象的内容时,我决定考虑的一些因素本身就会因系统而异。
也许我实际上是故意引入不同构建的不同种子来捕捉任何同事错误地依赖我的哈希码的情况! (我听说MS用string.GetHashCode()
的实现做了这个,但是不记得我是否从可靠或轻信的来源中听到了这一点。
主要是,这将是前两个原因之一。
现在,为什么我可以提供这样的保证?
如果我这么做的话,很可能是偶然的。如果可以仅基于单个整数id来比较元素的相等性,那么我将使用它作为我的哈希码。对于不太好的哈希,任何其他东西都会更有效。我不太可能改变这一点,所以我可以。
我可能的另一个原因是,我自己想要保证。没有什么可说的,我不能提供它,只是我没有。
好的,让我们做一些实用的事情。在某些情况下,您可能需要与机器无关的保证。有些情况下你可能会想要相反的情况,我会稍微谈谈。
首先,检查你的逻辑。你能处理碰撞吗?好的,那我们就开始吧。
如果它是你自己的类,那么实现以便提供这样的保证,记录它,你就完成了。
如果不是你的班级,那么以提供它的方式实施IEqualityComparer<T>
。例如:
public class ConsistentGuaranteedComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return x == y;
}
public int GetHashCode(string obj)
{
if(obj == null)
return 0;
int hash = obj.Length;
for(int i = 0; i != obj.Length; ++i)
hash = (hash << 5) - hash + obj[i];
return hash;
}
}
然后使用它而不是内置的哈希码。
有一个有趣的案例,我们可能想要相反。如果我可以控制你正在散列的字符串集,那么我可以选择一堆具有相同哈希码的字符串。你的基于哈希的集合的性能将会变得更糟,并且非常糟糕。我可以继续比你处理它更快地做到这一点,所以它可能是一种拒绝服务攻击。发生这种情况的情况并不多,但重要的是,如果您正在处理我发送的XML文档,您不能仅排除某些元素(许多格式允许其中的元素自由)。然后解析器中的NameTable
会受到伤害。在这种情况下,我们每次都创建一个新的哈希机制:
public class RandomComparer : IEqualityComparer<string>
{
private int hashSeed = Environment.TickCount;
public bool Equals(string x, string y)
{
return x == y;
}
public int GetHashCode(string obj)
{
if(obj == null)
return 0;
int hash = hashSeed + obj.Length;
for(int i = 0; i != obj.Length; ++i)
hash = hash << 5 - hash + obj[i];
hash += (hash << 15) ^ 0xffffcd7d;
hash ^= (hash >>> 10);
hash += (hash << 3);
hash ^= (hash >>> 6);
hash += (hash << 2) + (hash << 14);
return hash ^ (hash >>> 16)
}
}
这在给定的使用中是一致的,但从使用到使用不一致,因此攻击者无法构造输入以强制它为DoSsed。顺便说一下,NameTable
不使用IEqualityComparer<T>
,因为除非必要,否则它想要处理具有索引和长度的char数组而不构造字符串,但它确实做了类似的事情。
顺便说一下,在Java中,string
的哈希码被指定并且不会改变,但对于其他类可能不是这样。
编辑:我已经对上面ConsistentGuaranteedComparer
所采用的方法的整体质量进行了一些研究,我不再满足于在我的答案中使用这些算法;虽然它用于描述这个概念,但它并没有像人们想象的那样好。当然,如果一个人已经实现了这样的事情,那么在不违反保证的情况下就无法改变它,但如果我现在建议使用this library of mine, written after said research如下:
public class ConsistentGuaranteedComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return x == y;
}
public int GetHashCode(string obj)
{
return obj.SpookyHash32();
}
}
上述RandomComparer
的情况并不差,但也可以改进:
public class RandomComparer : IEqualityComparer<string>
{
private int hashSeed = Environment.TickCount;
public bool Equals(string x, string y)
{
return x == y;
}
public int GetHashCode(string obj)
{
return obj.SpookyHash32(hashSeed);
}
}
或者更难以预测:
public class RandomComparer : IEqualityComparer<string>
{
private long seed0 = Environment.TickCount;
private long seed1 = DateTime.Now.Ticks;
public bool Equals(string x, string y)
{
return x == y;
}
public int GetHashCode(string obj)
{
return obj.SpookyHash128(seed0, seed1).GetHashCode();
}
}
答案 1 :(得分:1)
即使在不同运行的同一台机器上, 也会产生不同的结果。
所以它基本上可以用来(并且它实际上是用来)在程序的当前运行期间检查某些内容,但是没有任何意义来存储它,以便在之后检查它。导致您获得的数字由运行时生成。
修改强>
对于字符串的特定情况,即使在不同的机器上,它也会产生相同的结果,除非机器具有不同的架构。