移除換行與空白字元
單行替換 (最常用)
String text = "第一行\n第二行\r\n第三行\t有Tab";
// 移除所有換行、回車、Tab
String clean = text.replaceAll("[\\n\\r\\t]", "");
// 結果: "第一行第二行第三行有Tab"
// 或使用 \\s 匹配所有空白字元 (含空格)
String compact = text.replaceAll("\\s+", "");
// 結果: "第一行第二行第三行有Tab"
// 保留單一空格合併多重空白
String normalized = text.replaceAll("\\s+", " ");
// 結果: "第一行 第二行 第三行 有Tab"
跨平台換行符處理
// 系統相關換行符
String osLineSep = System.lineSeparator(); // Windows: \r\n, Linux/Mac: \n
// 統一轉換為系統換行符
String normalized = text.replaceAll("\\r\\n|\\r|\\n", osLineSep);
// 讀取檔案時自動處理 (Java 7+)
List lines = Files.readAllLines(Paths.get("file.txt"), StandardCharsets.UTF_8);
// lines 已自動移除行尾換行符
正則表達式核心 API: Pattern 與 Matcher
基本使用流程
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RegexDemo {
public static void main(String[] args) {
String content = "訂單號: ORD-20240115-001, 金額: $1,234.56, 狀態: 已付款";
// 1. 編譯模式 (建議複用 Pattern 物件)
Pattern pattern = Pattern.compile("ORD-\\d{8}-\\d{3}");
// 2. 建立匹配器
Matcher matcher = pattern.matcher(content);
// 3. 查找所有匹配
while (matcher.find()) {
System.out.println("完整匹配: " + matcher.group(0)); // group(0) = 整個匹配
// 如果有捕獲組
// System.out.println("第1組: " + matcher.group(1));
}
// 替換應用
String masked = pattern.matcher(content).replaceAll("ORD-***-***");
System.out.println("遮罩後: " + masked);
}
}
Pattern 編譯旗標
| 旗標 | 常數 | 說明 |
(?i) | Pattern.CASE_INSENSITIVE | 忽略大小寫 |
(?m) | Pattern.MULTILINE | ^$ 匹配行首行尾 |
(?s) | Pattern.DOTALL | . 匹配含換行符 |
(?x) | Pattern.COMMENTS | 允許註解與空白 |
(?u) | Pattern.UNICODE_CHARACTER_CLASS | Unicode 感知類別 |
// 組合旗標
Pattern p = Pattern.compile("(?im)^error.*$"); // 忽略大小寫 + 多行模式
Pattern p2 = Pattern.compile("foo", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
常用正則模式速查
| 需求 | 正則 | 說明 |
| Email | ^[\\w.-]+@[\\w.-]+\\.\\w{2,}$ | 簡易版,RFC 5322 完整版極複雜 |
| 手機號 (台灣) | ^09\\d{8}$ | 09 開頭 10 碼 |
| 身分證字號 (台灣) | ^[A-Z][12]\\d{8}$ | 字母+性別碼+8數字 |
| IPv4 | ^((25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d?\\d)$ | 嚴格驗證 |
| 中文字元 | [\\u4e00-\\u9fa5] | 基本 CJK 區塊 |
| 日期 (yyyy-MM-dd) | ^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$ | 格式驗證,非邏輯驗證 |
| 密碼強度 | ^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$ | 大小寫+數字+特殊符號 8位以上 |
Matcher 進階技巧
群組捕獲與替換
String html = "商品A
商品B
";
Pattern p = Pattern.compile("(.*?)
");
Matcher m = p.matcher(html);
StringBuffer sb = new StringBuffer();
while (m.find()) {
// 將捕獲內容包裝在 span 中
m.appendReplacement(sb, "$1");
}
m.appendTail(sb);
System.out.println(sb.toString());
// 商品A商品B
零寬斷言 (Lookaround)
// 正向前瞻:匹配後面是數字的字母
"abc123 def".replaceAll("(?=\\d)", "|"); // "abc|123 def"
// 負向前瞻:匹配後面非數字的字母
"abc123 def".replaceAll("(?!\\d)", "|"); // "|a|b|c|1|2|3| |d|e|f|"
// 正向後顧:匹配前面是錢字號的數字
"$100 $200".replaceAll("(?<=\\$)\\d+", "***"); // "$*** $***"
// 負向後顧:匹配前面非錢字號的數字
"年2024 月01".replaceAll("(?
貪婪 vs 非貪婪 vs 佔有優先
String s = "bd";
// 貪婪 (預設):匹配最長
s.replaceAll("<.*>", ""); // "" (全匹配)
// 非貪婪 (?):匹配最短
s.replaceAll("<.*?>", ""); // "bd" (正確移除標籤)
// 佔有優先 (+):不回溯,效能最好但需確定模式
// "<.++>" 在此例會匹配失敗 (因 > 被佔有)
效能優化建議
- 預編譯 Pattern:靜態常量或物件欄位持有,避免重複編譯
- 避免過度回溯:巢狀量詞
(a+)+ 遇 aaaaX 會災難性回溯
- 使用具體字元類別:
[a-z] 優於 .,\\d 優於 [0-9]
- 錨定開頭結尾:
^pattern$ 避免全文掃描
- 複雜邏輯改用程式碼:正則難以維護時拆分為多步驟
Java 11+ 新增字串方法
// 判斷是否匹配 (整串)
"abc".matches("[a-z]+"); // true
// 分割 (支援正則)
"a,b;c".split("[,;]"); // ["a","b","c"]
// 替換首個
"foo bar foo".replaceFirst("foo", "baz"); // "baz bar foo"
// 字串分割器 (Stream API)
Pattern.compile(",")
.splitAsStream("a,b,c")
.map(String::trim)
.collect(Collectors.toList());