.net String.GetHashCode考虑整个字符串还是只考虑其中的一部分?

gk7wooem  于 2023-01-03  发布在  .NET
关注(0)|答案(2)|浏览(136)

我很好奇,因为我猜它会对性能有影响。它会考虑整个字符串吗?如果是的话,它在处理长字符串时会很慢。如果它只考虑字符串的一部分,它的性能会很差(例如,如果它只考虑字符串的开头,如果HashSet包含的大多数字符串具有相同的字符串,它的性能会很差)。

wgx48brx

wgx48brx1#

当你有这样的问题时,一定要获得Reference Source源代码。它比你从反编译器中看到的要多得多。选择一个与你喜欢的.NET目标匹配的源代码,这个方法在不同版本之间有很大的变化。我在这里只复制它的.NET 4.5版本。从源代码.NET 4.5\4.6.0.0\net\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位版本的框架时定义的,64位版本的mscorlib.dll是单独生成的,并存储在GAC的不同子目录中。
  • 变量s_UseRandomizedStringHashing启用了哈希算法的安全版本,旨在避免程序员做一些不明智的事情,如使用GetHashCode()生成密码或加密的哈希值。
  • fixed* 语句保持了为字符串建立索引的廉价性,避免了常规索引器执行的边界检查
  • 第一个Assert确保字符串以零结尾,这是允许循环中的优化所必需的
  • 第二个Assert确保字符串与4的倍数的地址对齐,这是保持循环性能所必需的
  • 循环是手动展开的,对于32位版本,每个循环消耗4个字符。转换为int* 是存储2个字符的技巧(2 x 16位),以整数形式(32位)。循环后的额外语句处理长度不是4的倍数的字符串。注意,散列中可能包含也可能不包含零终止符,如果长度是偶数,它就不会这样,它会查看字符串中的所有字符,回答你的问题
  • 这个循环的64位版本是不同的,手工展开了2。注意,它在嵌入的0处提前终止,所以不查看所有的字符。否则就很少见了。这很奇怪,我只能猜测这与字符串可能非常大有关。但是想不出一个实际的例子
  • 最后的调试代码确保框架中的任何代码都不会依赖于散列代码在运行之间的可重复性。
  • 散列算法是相当标准的,值1566083941是一个幻数,是Mersenne twister中常见的质数。
tez616oj

tez616oj2#

检查源代码(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;
}

相关问题