字符串是 Java 應用中使用最頻繁的類型,第 13 章深入剖析 String 不可變設計、拼接性能陷阱、格式化輸出、正則表達式引擎、Scanner 掃描器。本文系統整理核心知識與實戰最佳實踐。
String 不可變性:設計哲學與影響
- 不可變:
String 所有「修改」方法(substring、replace、toUpperCase、concat)均返回新對象,原對象不變。
- 好處:線程安全(無狀態)、可安全共享、哈希值可緩存(
hashCode 懶加載)、字符串常量池去重、安全性(網絡/文件路徑參數不可被竄改)。
- 代價:頻繁修改產生大量臨時對象,GC 壓力大。
拼接性能:+ 操作符 vs StringBuilder
// 循環中用 + → 每次迭代隱式 new StringBuilder() → append → toString() → 極慢
String s = "";
for (int i = 0; i < 10000; i++) s += i; // O(n²) 字符複製
// 正確:顯式 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) sb.append(i);
String result = sb.toString(); // O(n)
- 編譯器優化:單行
a + b + c 會自動優化為 StringBuilder,但循環/條件分支中無法優化。
- JDK 5 前用
StringBuffer(同步、慢),JDK 5+ 推薦 StringBuilder(非同步、快)。
- toString() 中循環拼接必須顯式用 StringBuilder。
格式化輸出:printf / format / Formatter
核心語法
System.out.printf("姓名:%s,年齡:%d,身高:%.2f%n", name, age, height);
// %[參數索引$][標誌][寬度][.精度]轉換符
常用轉換符
| 轉換符 | 對應類型 | 示例 |
%s | 任意 (toString) | "hello" |
%d | 整數 | 42 |
%f | 浮點數 | 3.14159 |
%.2f | 保留 2 位小數 | 3.14 |
%x / %X | 十六進制 | 2a / 2A |
%tX | 日期時間組合 | %tF → 2024-01-15 |
%n | 平台換行符 | Windows \r\n / Linux \n |
三種格式化 API
System.out.printf(format, args):直接輸出控制台。
String.format(format, args):返回格式化字符串(靜態方法,內部用 Formatter)。
new Formatter().format(format, args):底層引擎,可輸出到任意 Appendable(StringBuilder、文件、Socket)。
正則表達式:字符串處理的瑞士軍刀
String 內置便捷方法
"abc123def".matches("\\d+") // 整體匹配:false
"abc123def".split("\\d+") // 分割:["abc", "def"]
"abc123def".replaceAll("\\d+", "#") // 替換:"abc#def"
"abc123def".replaceFirst("\\d+", "#") // 首次替換:"abc#def"
Pattern & Matcher:高性能、複雜場景
Pattern p = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})"); // 編譯一次,多次復用
Matcher m = p.matcher("2024-01-15");
while (m.find()) {
System.out.println("年:" + m.group(1) + " 月:" + m.group(2) + " 日:" + m.group(3));
// group(0) 整個匹配,group(1..n) 捕獲組
}
量詞模式對比
| 模式 | 量詞 | 行為 | 示例輸入 | 匹配結果 |
| 貪婪 | * + ? {n,m} | 盡可能多匹配 | "aaab" / a*b | "aaa" |
| 勉強 | *? +? ?? {n,m}? | 盡可能少匹配 | "aaab" / a*?b | "" (空串) |
| 佔有 | *+ ++ ?+ {n,m}+ | 不回溯,失敗即失敗 | "aaab" / a*+b | 失敗(佔有所有 a) |
分組與邊界
(...) 捕獲組,(?:...) 非捕獲組(不佔 group 索引)。
m.start()/m.end() 獲取匹配區間,m.group(i) 獲取組內容。
matches() 整體匹配,lookingAt() 前缀匹配,find() 部分匹配(可多次調用遍歷)。
Scanner:掃描器統一輸入解析
Scanner sc = new Scanner(System.in); // 或 new Scanner(file) / new Scanner(string)
sc.useDelimiter("\\s+"); // 自定義分隔符(正則),默認空白字符
while (sc.hasNextInt()) {
int num = sc.nextInt();
System.out.println("讀到整數:" + num);
}
sc.close();
nextInt()、nextDouble()、nextLine()、next(Pattern) 等類型安全讀取。
- 分隔符僅作用於下一個 token,若模式包含分隔符永不會匹配。
- 關閉
Scanner 會關閉底層 Readable(如 System.in 將不可再讀),慎用。
實戰避坑指南
- 字符串常量池:
String s1 = "a"; String s2 = "a"; s1 == s2 → true;new String("a") 強制創建堆對象,== 比較引用,內容比較永遠用 equals()。
- 正則轉義:Java 字符串中
\ 需雙重轉義 \\,Pattern.quote(str) 自動轉義字面量。
- Scanner 陷阱:
nextInt() 後緊跟 nextLine() 會讀到空行(換行符未消費),解決:sc.nextLine() 先消費換行。
- 性能敏感場景:預編譯
Pattern、復用 Matcher、避免循環中 String.matches() 重複編譯。
總結
字符串處理核心三板斧:不可變認知 + StringBuilder 拼接 + 正則/格式化 API 選型。掌握 Pattern/Matcher 高級用法(分組、邊界、量詞模式),能解決 99% 文本解析需求。Scanner 適合簡單控制台/文件掃描,複雜解析請上正則或專用解析器(Jackson、Gson、ANTLR)。
- 上一篇文章: Java 入門筆記 001:Windows 環境搭建、DOS 基礎命令、JDK 安裝驗證
- 下一篇文章: 《Thinking in Java》讀書筆記:第 12 章 異常處理——核心機制、自定義異常、finally 陷阱、檢查型與非檢查型抉擇
Copyright ©
程式碼之家 All Rights Reserved