Dot-Net

為什麼 System.String 對像不會記憶體其雜湊碼?

  • June 16, 2010

瀏覽一下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));
   }
}

現在,我意識到的實現GetHashCode沒有指定並且是依賴於實現的,所以問題是“是以GetHashCodeX 還是 Y 的形式實現?” 不是真的可以回答。我只是對幾件事感到好奇:

  1. 如果 Reflector 正確地反彙編了 DLL 並且這(在我的環境中)的實現GetHashCode,我是否正確地解釋了這段程式碼以指示string基於這個特定實現的對像不會記憶體其雜湊碼?
  2. 假設答案是肯定的,為什麼會這樣?在我看來,記憶體成本將是最小的(多一個 32 位整數,與字元串本身的大小相比減少了),而節省的成本將是顯著的,尤其是在使用字元串的情況下作為基於雜湊表的集合中的鍵,例如Dictionary<string, [...]>. 而且由於string該類是不可變的,因此返回的值GetHashCode甚至不會改變。

我會錯過什麼?


更新:回應安德拉斯佐爾坦的閉幕詞:

蒂姆的回答(+1 那裡)也很重要。如果他是對的,而且我認為他是對的,那麼就不能保證一個字元串在構造後實際上是不可變的,因此記憶體結果是錯誤的。

哇哇哇!_ 這是一個有趣的觀點(是的,這是非常正確的),但我真的懷疑GetHashCode. “因此記憶體結果是錯誤的”這句話對我來說意味著框架對字元串的態度是“好吧,它們應該是不可變的,但如果開發人員想要偷偷摸摸,它們是可變的,所以我們會處理他們就是這樣。” 這絕對不是框架查看字元串的方式。它在很多方面完全依賴於它們的不變性(字元串文字的實習,將所有零長度字元串分配給string.Empty等),基本上,如果你改變一個字元串,你正在編寫其行為完全未定義和不可預測的程式碼。

我想我的意思是讓這個實現的作者擔心,“如果這個字元串實例在呼叫之間被修改了怎麼辦,即使它公開暴露的類是不可變的?” 就像一個計劃休閒戶外燒烤的人會想他/她自己,“如果有人把原子彈帶到聚會上怎麼辦?” 聽著,如果有人帶了原子彈,派對就結束了。

明顯的潛在答案:因為那會消耗記憶體。

這裡有一個成本/收益分析:

成本:每個字元串 4 個字節(以及對每次呼叫 GetHashCode 的快速測試)。還要使字元串對象可變,這顯然意味著您需要小心實現 - 除非您總是預先計算雜湊碼,這是為每個字元串計算一次的成本,無論您是否曾經雜湊它。

好處:避免為多次散列的字元串值重新計算散列值

我建議在許多情況下,有很多很多的字元串對象,其中很少有人會多次散列 - 導致淨成本。在某些情況下,顯然情況並非如此。

我認為我無法判斷哪個出現頻率更高……我希望 MS 已經檢測了各種真實的應用程序。(我也希望 Sun 對 Java 做同樣的事情,它確實記憶體了雜湊……)

編輯:我剛剛和 Eric Lippert 談過這個問題(NDC 很棒:),基本上關於額外的記憶體命中與有限的好處。

引用自:https://stackoverflow.com/questions/3053726