為什麼 System.String 對像不會記憶體其雜湊碼?
瀏覽一下
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 的形式實現?” 不是真的可以回答。我只是對幾件事感到好奇:
- 如果 Reflector 正確地反彙編了 DLL 並且這是(在我的環境中)的實現
GetHashCode,我是否正確地解釋了這段程式碼以指示string基於這個特定實現的對像不會記憶體其雜湊碼?- 假設答案是肯定的,為什麼會這樣?在我看來,記憶體成本將是最小的(多一個 32 位整數,與字元串本身的大小相比減少了),而節省的成本將是顯著的,尤其是在使用字元串的情況下作為基於雜湊表的集合中的鍵,例如
Dictionary<string, [...]>. 而且由於string該類是不可變的,因此返回的值GetHashCode甚至不會改變。我會錯過什麼?
更新:回應安德拉斯佐爾坦的閉幕詞:
蒂姆的回答(+1 那裡)也很重要。如果他是對的,而且我認為他是對的,那麼就不能保證一個字元串在構造後實際上是不可變的,因此記憶體結果是錯誤的。
哇哇哇!_ 這是一個有趣的觀點(是的,這是非常正確的),但我真的懷疑在
GetHashCode. “因此記憶體結果是錯誤的”這句話對我來說意味著框架對字元串的態度是“好吧,它們應該是不可變的,但如果開發人員想要偷偷摸摸,它們是可變的,所以我們會處理他們就是這樣。” 這絕對不是框架查看字元串的方式。它在很多方面完全依賴於它們的不變性(字元串文字的實習,將所有零長度字元串分配給string.Empty等),基本上,如果你改變一個字元串,你正在編寫其行為完全未定義和不可預測的程式碼。我想我的意思是讓這個實現的作者擔心,“如果這個字元串實例在呼叫之間被修改了怎麼辦,即使它公開暴露的類是不可變的?” 就像一個計劃休閒戶外燒烤的人會想他/她自己,“如果有人把原子彈帶到聚會上怎麼辦?” 聽著,如果有人帶了原子彈,派對就結束了。
明顯的潛在答案:因為那會消耗記憶體。
這裡有一個成本/收益分析:
成本:每個字元串 4 個字節(以及對每次呼叫 GetHashCode 的快速測試)。還要使字元串對象可變,這顯然意味著您需要小心實現 - 除非您總是預先計算雜湊碼,這是為每個字元串計算一次的成本,無論您是否曾經雜湊它。
好處:避免為多次散列的字元串值重新計算散列值
我建議在許多情況下,有很多很多的字元串對象,其中很少有人會多次散列 - 導致淨成本。在某些情況下,顯然情況並非如此。
我認為我無法判斷哪個出現頻率更高……我希望 MS 已經檢測了各種真實的應用程序。(我也希望 Sun 對 Java 做同樣的事情,它確實記憶體了雜湊……)
編輯:我剛剛和 Eric Lippert 談過這個問題(NDC 很棒:),基本上是關於額外的記憶體命中與有限的好處。