我只是好奇,因为我想这会对性能产生影响。 是否考虑完整的字符串? 如果是的话,这将是长长的一串缓慢。 如果只考虑字符串的一部分,将有糟糕的表现(例如,如果只考虑字符串的开头,如果HashSet的主要成分是用相同的字符串将有糟糕的表现。
Answer 1:
一定要得到参考源代码 ,当你有这样的问题。 有很多比你可以从反编译器看到更多的东西。 挑选符合您的首选.NET目标之一,该方法已经改变版本之间很大。 我只是重现.NET 4.5版本的在这里,从Source.NET 4.5 \ 4.6.0.0 \网络\ CLR \ SRC \ BCL \ SYSTEM \ String.cs \ 604718个\ String.cs检索
public override int GetHashCode() {
#if FEATURE_RANDOMIZED_STRING_HASHING
if(HashHelpers.s_UseRandomizedStringHashing)
{
return InternalMarvin32HashString(this, this.Length, 0);
}
#endif // FEATURE_RANDOMIZED_STRING_HASHING
unsafe {
fixed (char *src = this) {
Contract.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'");
Contract.Assert( ((int)src)%4 == 0, "Managed string should start at 4 bytes boundary");
#if WIN32
int hash1 = (5381<<16) + 5381;
#else
int hash1 = 5381;
#endif
int hash2 = hash1;
#if WIN32
// 32 bit machines.
int* pint = (int *)src;
int len = this.Length;
while (len > 2)
{
hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1];
pint += 2;
len -= 4;
}
if (len > 0)
{
hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
}
#else
int c;
char *s = src;
while ((c = s[0]) != 0) {
hash1 = ((hash1 << 5) + hash1) ^ c;
c = s[1];
if (c == 0)
break;
hash2 = ((hash2 << 5) + hash2) ^ c;
s += 2;
}
#endif
#if DEBUG
// We want to ensure we can change our hash function daily.
// This is perfectly fine as long as you don't persist the
// value from GetHashCode to disk or count on String A
// hashing before string B. Those are bugs in your code.
hash1 ^= ThisAssembly.DailyBuildNumber;
#endif
return hash1 + (hash2 * 1566083941);
}
}
}
这是比你讨价还价的可能更多,我会注释代码位:
- 在#if条件编译指令适应这个代码到不同的.NET目标。 该FEATURE_XX标识符别处定义和转功能关闭整个.NET源代码的整体销售。 WIN32是当目标是框架的32位版本的定义,mscorlib.dll中的64位版本被单独构建并存储在不同的子目录中的GAC。
- 该s_UseRandomizedStringHashing变量使散列算法,目的是让程序员摆脱麻烦的是做一些不明智喜欢使用GetHashCode()方法生成的东西像密码或加密散列的安全版本。 它通过启用一个条目在app.exe.config文件
- 固定语句保持索引串便宜,避免了常规检查索引进行边界
- 第一断言确保字符串零封端的,因为它应该是,要求允许在循环的最优化
- 所述第二断言确保串对齐到这是4的倍数,因为它应,需要保持环路高性能的地址
- 环由手展开,消耗每循环4个字符的32位版本。 铸到INT *是存储2个字符(2×16比特)的INT(32位)一招。 循环处理字符串的长度不是4.注意0终结可能会或可能不会被包括在散列多之后,额外的语句,也不会是,如果长度为偶数。 它着眼于字符串中的所有字符,回答你的问题
- 环路的64位版本不同的做法,手展开由2注意,早期的嵌入式零终止,所以不看的所有字符。 否则,很罕见。 这很奇怪,我只能猜想,这是与字符串可能是非常大的。 但想不到的实际例子的
- 在最后的调试代码确保在框架无码有史以来承担的哈希码是运行之间的可重复性的依赖。
- 散列算法是非常标准。 值1566083941是一个神奇的数字,一个主要是共同的梅森捻线机 。
Answer 2:
检查源代码(礼貌ILSpy ),我们可以看到,它遍历字符串的长度。
// string
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), SecuritySafeCritical]
public unsafe override int GetHashCode()
{
IntPtr arg_0F_0;
IntPtr expr_06 = arg_0F_0 = this;
if (expr_06 != 0)
{
arg_0F_0 = (IntPtr)((int)expr_06 + RuntimeHelpers.OffsetToStringData);
}
char* ptr = arg_0F_0;
int num = 352654597;
int num2 = num;
int* ptr2 = (int*)ptr;
for (int i = this.Length; i > 0; i -= 4)
{
num = ((num << 5) + num + (num >> 27) ^ *ptr2);
if (i <= 2)
{
break;
}
num2 = ((num2 << 5) + num2 + (num2 >> 27) ^ ptr2[(IntPtr)4 / 4]);
ptr2 += (IntPtr)8 / 4;
}
return num + num2 * 1566083941;
}
文章来源: How is GetHashCode() of C# string implemented?