首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> C# 入門

.NET 字串駐留機制深度解析:編譯期、執行期、Intern Pool 與效能影響

     編輯:C# 入門

現象:為什麼兩個相同字串變數 ReferenceEquals 為 true?

string s1 = "ABCD1234";
string s2 = "ABCD1234";
Console.WriteLine(object.ReferenceEquals(s1, s2)); // True ✅

string s3 = "ABCD";
string s4 = "1234";
string s5 = "ABCD" + "1234";        // 編譯期常數摺疊
string s6 = "ABCD" + s4;            // 執行期拼接
string s7 = s3 + s4;                // 執行期拼接

Console.WriteLine(object.ReferenceEquals(s1, s5)); // True  ✅
Console.WriteLine(object.ReferenceEquals(s1, s6)); // False ❌
Console.WriteLine(object.ReferenceEquals(s1, s7)); // False ❌

// 手動駐留
Console.WriteLine(object.ReferenceEquals(s1, string.Intern(s6))); // True
Console.WriteLine(object.ReferenceEquals(s1, string.Intern(s7))); // True

核心機制:字串駐留池

.NET 維護一張全域哈希表 Intern Pool (字串駐留池),Key 為字串內容,Value 為該字串在托管堆上的唯一引用。

  • 編譯期常數 (Literal + 常數摺疊結果) → **自動駐留** 進池。
  • 執行期動態建立 的字串 (new StringBuilder、拼接、讀取檔案、網路接收) → **預設不駐留**,每次新建物件。
  • string.Intern(str):若池中已有相同內容,返回池中引用;否則將 str 加入池並返回其引用。
  • string.IsInterned(str):檢查池中是否存在,存在返回引用,否則返回 null (不加入)。

詳細案例分析

程式碼 類型 是否自動駐留 說明
"ABCD1234" 字串常值 ✅ 是 編譯期寫入模組中繼資料 #US heap,模組載入時自動進 Intern Pool
const string C = "ABC"; 常數 ✅ 是 同字串常值
"AB" + "CD" 常數摺疊 ✅ 是 編譯器計算結果 "ABCD" 視為常值
s3 + s4 (變數拼接) 執行期運算 ❌ 否 每次執行產生新物件,除非 JIT 極罕見優化
string.Concat(...) 方法調用 ❌ 否 同執行期拼接
new StringBuilder().ToString() 建構器 ❌ 否 新物件
string.Intern(s) 手動駐留 ✅ 手動 強制進池/獲取池中引用

效能與記憶體影響

優點

  • 比較優化: ReferenceEquals / == (參考比較) 極快,可作為 Equals (內容比較) 的快速路徑。
  • 記憶體去重: 大量重複字串 (如 XML 標籤、日誌關鍵字、Enum.ToString()) 只佔一份記憶體。

缺點與風險

  • 池永不釋放: Intern Pool 隨 AppDomain 存亡 (進程結束才釋放)。大量動態字串呼叫 Intern() 會導致**記憶體洩漏** (池膨脹不可回收)。
  • string.Intern 自身有鎖競爭開銷,高併發下成為瓶頸。
  • 預設行為不一致:跨 AppDomain (舊 .NET Framework) 池隔離,.NET Core 單一進程單一池。

最佳實踐指南

  1. 不要手動 Intern 使用者輸入、網路資料、大量動態字串 → 記憶體洩漏隱患。
  2. 比較字串內容請用 string.Equals(a, b, StringComparison.Ordinal) 或 a == b (已重載為內容比較),JIT 會優化先比引用再比內容,無需手動干預。
  3. 固定集合的鍵值查找 (如編譯器符號表、關鍵字集合) 可考慮 Intern 節省記憶體,但集合必須固定且有限。
  4. Enum.ToString() / nameof / typeof().Name 等編譯期可確定字串自動駐留,無需擔心。
  5. 需要字串去重快取時,改用 ConcurrentDictionary<string, string> 自行管理,可控生命週期與淘汰策略。

進階:.NET Core / .NET 5+ 的變化

  • 單一進程單一 Intern Pool (無 AppDomain 隔離)。
  • string.IsInterned / Intern 實作優化,鎖粒度更細。
  • 編譯器常數摺疊更積極 (C# 10+ 插值字串常數部分摺疊)。
  • 引入 StringBuilder.GetChunks 等避免中間字串分配,減少動態字串產生。

總結

字串駐留是 .NET 執行期為**編譯期常數**提供的記憶體去重與比較優化機制。**執行期動態字串預設不駐留**。理解這一機制能解釋 ReferenceEquals 的反直覺行為,但**切勿濫用 string.Intern() 處理動態資料**,應依賴 JIT 對 ==/Equals 的雙重檢查優化,或自行管理弱引用快取。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved