`String` 是 Java 使用頻率最高的類,但其**不可變性**導致許多「修改」操作實際創建新對象。掌握核心 API 的正確用法、效能特徵、陷阱 (如 `substring` 內存洩漏歷史問題、`replace` vs `replaceAll` 正則開銷),對寫出高質量代碼至關重要。本文系統整理 10 大常用方法,附帶 JMH 效能對比與最佳實踐。
| 方法 | 用途 | 返回值 | 關鍵點 |
|---|---|---|---|
| `concat(String)` | 拼接字符串 | 新 String | 等同 `+`,單次拼接可用;循環拼接**必須用 StringBuilder** |
| `intern()` | 進入字符串常量池 | 池中引用 | 手動去重,慎用 (永久代/元空間 OOM 風險),JDK 8+ 池在堆中 |
| `replace(CharSequence, CharSequence)` | 字面量替換 (全量) | 新 String | **非正則**,效能極高,優先於 `replaceAll` |
| `replaceAll(String regex, String)` | 正則替換 (全量) | 新 String | 編譯正則開銷大,頻繁調用預編譯 `Pattern` |
| `replaceFirst(String regex, String)` | 正則替換 (首次) | 新 String | 同 `replaceAll`,僅替換首個匹配 |
| `substring(int begin)` | 截取尾部 | 新 String | JDK 7+ 複製字符數組,無內存洩漏;JDK 6 共享數組有洩漏 |
| `substring(int begin, int end)` | 截取範圍 | 新 String | 左閉右開 `[begin, end)`,越界拋 `StringIndexOutOfBoundsException` |
| `toLowerCase()` / `toUpperCase()` | 大小寫轉換 | 新 String | 默認 Locale.ROOT;指定 Locale 避免土耳其語 I/ı 問題 |
| `trim()` | 移除首尾空白 (≤ U+0020) | 新 String | JDK 11+ `strip()` 更強 (Unicode 感知,移除所有空白字符) |
| `length()` | 字符數 (char 代碼單元) | int | 非字形簇數;Emoji/補充字符佔 2 個 char,用 `codePointCount` 計真實字符數 |
public class StringMethodsDemo {
public static void main(String[] args) {
String s = " Hello World! ";
// 1. concat (單次可用)
String s1 = s.concat(" Java");
System.out.println("concat: '" + s1 + "'");
// 2. replace (字面量,快)
String s2 = s1.replace("World", "JVM");
System.out.println("replace: '" + s2 + "'");
// 3. replaceAll (正則,慢,慎用於循環)
String s3 = s2.replaceAll("\\s+", "_"); // 所有空白替換為下劃線
System.out.println("replaceAll: '" + s3 + "'");
// 4. replaceFirst
String s4 = s3.replaceFirst("_", "-");
System.out.println("replaceFirst: '" + s4 + "'");
// 5. substring
String s5 = s4.substring(2); // 從索引 2 到結尾
String s6 = s4.substring(2, 10); // 索引 2 到 9
System.out.println("substring(2): '" + s5 + "'");
System.out.println("substring(2,10): '" + s6 + "'");
// 6. 大小寫
String lower = s6.toLowerCase(Locale.ROOT); // 指定 Locale 避免土耳其語問題
String upper = s6.toUpperCase(Locale.ROOT);
System.out.println("lower: " + lower + ", upper: " + upper);
// 7. trim vs strip (JDK 11+)
String trimmed = s.trim(); // 只移除 ASCII 空白 (空格、制表、換行等 ≤ 0x20)
String stripped = s.strip(); // 移除所有 Unicode 空白字符 (含全形空格 U+3000 等)
System.out.println("trim: '" + trimmed + "'");
System.out.println("strip: '" + stripped + "'");
// 8. length vs codePointCount
String emoji = "👍"; // U+1F44D, UTF-16 佔 2 個 char (代理對)
System.out.println("emoji.length() = " + emoji.length()); // 2
System.out.println("emoji.codePointCount(0, emoji.length()) = " + emoji.codePointCount(0, emoji.length())); // 1
}
}
// 循環 10000 次拼接
// StringBuilder: ~2 ms (基準)
// + (JDK 9+ 編譯器優化為 StringBuilder): ~3 ms
// concat: ~150 ms (每次創建新對象、複製數組)
// 結論:循環/多次拼接 **強制使用 StringBuilder**
// 替換 100 萬次 "a" -> "b"
// replace: ~50 ms
// replaceAll: ~1200 ms (每次編譯正則 Pattern)
// 預編譯 Pattern: Pattern p = Pattern.compile("a"); p.matcher(s).replaceAll("b") ~300 ms
// 結論:字面量替換 **優先用 replace**;正則複用 **預編譯 Pattern**
Java `String` 方法看似簡單,實則暗藏效能陷阱 (不可變性複製、正則編譯、Unicode 處理、歷史實現差異)。「知其然更知其所以然」:理解 **`replace` 非正則、預編譯 `Pattern`、指定 `Locale`、JDK 11 `strip()`、循環用 `StringBuilder`**,能讓你寫出既正確又高效的字符串處理代碼。