在 HotSpot JVM 中,一般 Java 物件的記憶體佈局包含物件頭(8 位元組)、基本型別資料、參考(4 位元組)以及對齊填充。String 類別在 JDK 6 中擁有 char[] value、int offset、int count、int hash 四個欄位。一個空的 char[] 陣列本身也是物件,含物件頭 8 位元組加長度 4 位元組,共 12 位元組,經對齊後佔 16 位元組。因此一個空 String 物件大約佔用 40 位元組(物件頭 8 + 陣列 16 + 三個 int 12 + 陣列參考 4)。實際 String 佔用空間可依字元長度 n 估算:8 * (int)(((n * 2) + 43) / 8) 位元組。
substring 導致的記憶體洩漏某專案需解析 300 MB CSV 檔案,統計約 2 萬個唯一單詞(平均長度 15 字元)。依公式估算僅需 1.5 MB,但實際卻佔用數百 MB。主因在於大量使用 String.split() 或 substring() 取得單詞。JDK 6 中 substring 會共用原始 char[],僅透過 offset、count 記錄範圍。若從巨大字串截取少量子字串,原始大字串無法被 GC 回收,造成嚴重記憶體浪費。
呼叫 new String(smallString.toCharArray()) 可建立只包含所需字元的新字串,斷開對原始大字串的引用。雖此法在截取總長度接近原文本時空間複雜度較高(O(n³)),但於「從大文本截取少量字串」的場景極為有效。
+,編譯器會最佳化為單一常數。StringBuilder(單執行緒)或 StringBuffer(多執行緒)的 append,避免產生大量暫時 String 物件。String s = "Hello":優先從字串常數池取得實例,不存在才建立。String s = new String("Hello"):每次都在堆上建立新物件,若常數池已有相同內容則再複製一份。不建議使用 intern(),因它使用永久代(PermGen)且為全域池,易導致 OOM。較佳做法是自行維護 HashMap<String, String> 做快取:
private String getCacheWord(String key) {
String tmp = cacheMap.get(key);
if (tmp != null) return tmp;
cacheMap.put(key, key);
return key;
}
理解 String 記憶體模型、substring 共用陣列機制、拼接與建構的最佳實踐,並配合自訂快取,能有效解決大文本處理中的記憶體壓力。這些原理亦可推廣至其他 String 相關效能調優場景。