可以斷言,字符串操作是計算機程序設計中最常見、最基礎的行為。在 Java 中,字符串處理更是佔據了日常開發的絕大多數場景。本文將深入剖析 Java 中三大字符串類 —— String、StringBuffer、StringBuilder 的底層機制、核心差異,並通過源碼級分析與實戰測試,揭示字符串拼接的效能真相,幫助開發者在實際項目中做出最優選擇。
String 並非基本數據類型,而是一個 final 修飾的類,意味著它不可被繼承。更關鍵的是,String 對象是 不可變 的。查看 JDK 源碼或文檔會發現,幾乎所有看似修改字符串的操作(如 substring()、replace()、concat()),實際上都是創建了一個全新的 String 對象返回,原對象保持不變。
初始化前,字符串引用為 null,表示對象引用尚未創建,也未分配內存空間。而 "" 和 new String() 則表示已創建對象引用,並在堆內存中分配了空間,只是內部字符數組為空。打個比方:null 如同真空狀態,而 "" 和 new String() 則像裝有空氣的空玻璃杯 —— 雖然看起來「空」,但確實佔據了物理空間。
Java 中存在一個特殊的 字符串常量池(String Pool),位於方法區。當我們使用直接賦值方式創建字符串(如 String s = "aa")時,JVM 會先檢查池中是否已存在內容相同的字符串:
這一機制極大提升了內存利用率與創建效率。因此,強烈推薦使用直接賦值方式創建字符串,除非確有必要(如需明確創建獨立對象用於同步鎖等場景),才使用 new String("aa")。
equals():比較內容是否相同compareTo():比較字符串大小關係(按字典序)compareToIgnoreCase():忽略大小寫比較大小==:比較引用地址與內容(通常不用於業務邏輯比較)equalsIgnoreCase():忽略大小寫比較內容regionMatches():比較子字符串區域是否相同charAt(int index):返回指定索引處字符indexOf(String str) / indexOf(String str, int fromIndex):正向查找首次出現位置lastIndexOf(String str) / lastIndexOf(String str, int fromIndex):反向查找最後出現位置startsWith(String prefix) / startsWith(String prefix, int toffset):判斷是否以指定前綴開始endsWith(String suffix):判斷是否以指定後綴結束substring(int beginIndex):從 beginIndex 截取到末尾substring(int beginIndex, int endIndex):截取 [beginIndex, endIndex) 區間replace(char oldChar, char newChar):字符替換replace(CharSequence target, CharSequence replacement):序列替換replaceAll(String regex, String replacement):基於正則表達式的替換,注意首參數為正則,易誤用導致效能問題StringBuffer 同樣用於存儲字符串,但內部實現為 可變字符數組。其核心優勢在於:修改操作(追加、插入、刪除)直接在原對象上進行,不會產生新對象,因此在頻繁修改場景下內存效率遠優於 String。
方法命名與 String 相似,但語義不同:StringBuffer 的方法修改的是對象自身,而 String 返回新對象。此外,StringBuffer 不能使用直接賦值初始化,必須通過構造方法創建實例。
append():追加內容到末尾,類似字符串拼接,但修改自身insert():在指定位置插入內容delete():移除指定範圍內容StringBuilder 同樣是可變字符序列,與 StringBuffer API 几乎完全一致,核心區別在於:它不是線程安全的。基於此,StringBuilder 省去了同步鎖的開銷,單線程環境下速度通常快於 StringBuffer。其核心操作同樣以 append() 和 insert() 為主,能高效將各類數據轉為字符串並添加/插入。

關於 String 是否線程安全:因其不可變,所有操作均不改變值,從這角度看永遠安全;但若強行說「線程安全」,則因內容不可變,天然免疫併發問題。
並非所有 String 拼接都比 StringBuffer 慢。對於編譯期常量拼接:
String name = "I " + "am " + "chenssy";
JVM 會在編譯期優化為 String name = "I am chenssy";,直接生成單一字符串常量,速度極快。但若拼接中包含變量:
String s = "a";
for (int i = 0; i < 100000; i++) {
s += "b"; // 每次循環都會 new StringBuilder().append().toString()
}
此時會創建 10 萬個 StringBuilder 對象並調用 toString(),效能極差。這正是為什麼循環拼接必須使用 StringBuilder 或 StringBuffer 的原因。
Java 提供三種拼接方式:+、concat()、append()。實測 10 萬次循環拼接結果:
+ 運算符耗時:19080 毫秒
concat() 耗時:9089 毫秒
append() 耗時:10 毫秒
編譯器將 str += "b" 轉為:
str = new StringBuilder(str).append("b").toString();
每次循環 創建新 StringBuilder + append + toString(),產生大量臨時對象,GC 壓力巨大,故最慢。
源碼顯示其內部創建新字符數組,拷貝原數組與追加內容,最後 return new String(...)。同樣創建了 10 萬個字符串對象,數組拷貝雖快但對象創建開銷仍大。
StringBuffer.append() 調用父類 AbstractStringBuilder.append(),同樣進行數組擴容與拷貝,但關鍵在於 返回 this,不創建新對象。10 萬次循環僅操作同一對象,無 GC 壓力,故最快。
+ 可讀性最好,無需優化;concat() 無顯著優勢,不推薦常規使用。理解 String 不可變性、常量池機制,掌握 StringBuffer/StringBuilder 可變特性與線程安全差異,洞悉編譯器對 + 的優化邊界與陷阱,是寫出高效、穩健 Java 代碼的基礎。選型口訣:常用 String,單線程高頻用 StringBuilder,多線程高頻用 StringBuffer,循環拼接拒絕 + 號。