string.GetHashCode
的源代码一览使用Reflector显示以下内容(对于 mscorlib.dll 版本 4.0):
public override unsafe int GetHashCode()
{
fixed (char* str = ((char*) this))
{
char* chPtr = str;
int num = 0x15051505;
int num2 = num;
int* numPtr = (int*) chPtr;
for (int i = this.Length; i > 0; i -= 4)
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
if (i <= 2)
{
break;
}
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
numPtr += 2;
}
return (num + (num2 * 0x5d588b65));
}
}
现在,我意识到the implementation of GetHashCode
is not specified and is implementation-dependent ,所以问题“GetHashCode
是以X还是Y的形式实现的?”并不真正负责。我只是对一些事情感到好奇:
- 如果 Reflector 已正确反汇编 DLL,并且这是
GetHashCode
的实现(在我的环境中),我对这段代码的解释是否正确,表明string
基于此特定实现的对象不会缓存其哈希码? - 假设答案是肯定的,为什么会这样?在我看来,内存成本是最小的(多一个 32 位整数,与字符串本身的大小相比,简直就是小菜一碟),而节省的空间却是巨大的,特别是在使用字符串等情况下作为基于哈希表的集合中的键,例如
Dictionary<string, [...]>
。自从string
类是不可变的,它不像GetHashCode
返回的值甚至会改变。
我可能会错过什么?
<小时/>更新:回应 Andras Zoltan 的结束语:
There's also the point made in Tim's answer(+1 there). If he's right, and I think he is, then there's no guarantee that a string is actually immutable after construction, therefore to cache the result would be wrong.
哇,哇就在那里!这是一个有趣的观点(和 yes it's very true ),但我真的怀疑在实现 GetHashCode
时考虑到了这一点。 。 “因此缓存结果是错误的”这一说法对我来说意味着框架对字符串的态度是“嗯,它们应该是不可变的,但实际上,如果开发人员想要偷偷摸摸,他们就会这样做”它们是可变的,所以我们会这样对待它们。” 这绝对不是框架查看字符串的方式。它在很多方面完全依赖于它们的不变性(字符串文字的驻留、将所有零长度字符串分配给 string.Empty
等),基本上,如果你改变一个字符串,你正在编写的代码的行为完全是未定义且不可预测。
我想我的观点是,这个实现的作者会担心,“如果这个字符串实例在调用之间被修改怎么办,即使公开暴露的类是不可变的?”就像一个计划进行休闲户外烧烤的人会自言自语:“如果有人带着原子弹参加聚会怎么办?”听着,如果有人带来原子弹,派对就结束了。
最佳答案
明显的潜在答案:因为这会消耗内存。
这里有一个成本/效益分析:
成本:每个字符串 4 个字节(以及每次调用 GetHashCode 的快速测试)。还要使字符串对象可变,这显然意味着您需要小心 实现 - 除非您总是预先计算哈希码,这是为每个字符串计算一次的成本,无论您是否曾经对它进行哈希处理。
好处:避免为多次散列的字符串值重新计算散列
我建议,在很多情况下,有很多很多字符串对象,其中很少有被多次哈希的 - 导致净成本。对于某些情况,显然情况并非如此。
我认为我无法判断哪个出现的频率更高......我希望微软已经对各种真实的应用程序进行了检测。 (我也希望 Sun 对 Java 做同样的事情,它确实缓存哈希......)
编辑:我刚刚和 Eric Lippert 谈过这个问题(NDC 很棒:),基本上它是关于额外的内存消耗与有限的好处。
关于.net - 为什么 System.String 对象不缓存其哈希码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3053726/