簡體   English   中英

如何實現C#字符串的GetHashCode()?

[英]How is GetHashCode() of C# string implemented?

我只是很好奇,因為我猜它會影響性能。 它是否考慮完整的字符串? 如果是,長字符串會很慢。 如果它只考慮字符串的一部分,它將具有不良的性能(例如,如果它只考慮字符串的開頭,如果HashSet主要包含具有相同的字符串,則它將具有不良性能。

如果您有這樣的問題,請務必獲取參考源代碼 除了從反編譯器中看到的內容之外,還有很多內容。 選擇與您首選的.NET目標匹配的方法,該方法在版本之間發生了很大變化。 我將在這里重現它的.NET 4.5版本,從Source.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位版本的框架時定義的,mscorlib.dll的64位版本是單獨構建的,並存儲在GAC的不同子目錄中。
  • s_UseRandomizedStringHashing變量啟用了哈希算法的安全版本,旨在使程序員擺脫麻煩,做一些不明智的事情,比如使用GetHashCode()為密碼或加密等事件生成哈希值。 它由app.exe.config文件中的條目啟用
  • fixed語句保持索引字符串便宜,避免了常規索引器完成的邊界檢查
  • 第一個Assert確保字符串應該是零終止,這是允許在循環中進行優化所必需的
  • 第二個Assert確保字符串與一個4的倍數對齊,這是保持循環性能所必需的
  • 循環手動展開,每個循環消耗4個字符用於32位版本。 轉換為int *是一種在int(32位)中存儲2個字符(2 x 16位)的技巧。 循環之后的額外語句處理長度不是4的倍數的字符串。注意零終止符可能包含也可能不包含在哈希中,如果長度是偶數則不會。 它會查看字符串中的所有字符,回答您的問題
  • 循環的64位版本以不同方式完成,由2手動展開。請注意,它在嵌入的零點上提前終止,因此不會查看所有字符。 否則非常罕見。 這很奇怪,我只能猜測這與可能非常大的字符串有關。 但想不出一個實際的例子
  • 最后的調試代碼確保框架中的代碼不會依賴於在運行之間可重現的哈希代碼。
  • 哈希算法非常標准。 值1566083941是一個神奇的數字,是梅森捻線機中常見的素數。

檢查源代碼(由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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM