正則表達式是文本處理的利器。本文基於 java.util.regex 套件,系統講解 **Pattern 編譯與快取、Matcher 三大匹配模式、分組捕獲與替換、常用字符類/量詞/邊界/斷言速查表、貪婪/懶惰/獨佔模式、效能優化與災難性回溯防範**,附完整工具類與實戰案例。
Pattern pattern = Pattern.compile(regex); // 編譯 (建議預編譯快取)
Matcher matcher = pattern.matcher(input); // 建立匹配器
// 1. 完全匹配
boolean full = matcher.matches(); // 整個輸入必須匹配
// 2. 尋找子串 (可多次調用)
while (matcher.find()) { // 找到下一個匹配
String group = matcher.group(); // 整個匹配
String g1 = matcher.group(1); // 第 1 組
int start = matcher.start(); int end = matcher.end();
}
// 3. 部分匹配 (從開頭開始,不要求結尾)
boolean looking = matcher.lookingAt();
| 類別 | 符號 | 說明 | 範例 |
|---|---|---|---|
| 字符類 | . | 任意字符 (除換行) | a.c 匹配 abc, axc |
\d | 數字 [0-9] | \d{3} 三位數 | |
\w | 單詞字符 [a-zA-Z0-9_] | \w+ 單詞 | |
\s | 空白字符 (空格/製表/換行) | \s* 任意空白 | |
[abc] | 字符集合 | [aeiou] 元音 | |
[^abc] | 排除字符集合 | [^0-9] 非數字 | |
| 量詞 | * | 0 或多次 (貪婪) | ab*c ac, abc, abbc |
+ | 1 或多次 | \d+ 至少一位數 | |
? | 0 或 1 次 | colou?r color/colour | |
{n} | 恰好 n 次 | \d{4} 四位數 | |
{n,m} | n 到 m 次 | \d{3,5} 3~5 位數 | |
| 邊界/斷言 | ^ $ | 行首/行尾 (多行模式) | ^Start |
\b | 單詞邊界 | \bcat\b 整詞 cat | |
(?=...) (?!...) | 正向/負向前瞻 (零寬) | \d+(?=元) 數字後跟「元」 | |
| 分組 | (...) | 捕獲組 (可反向引用 \1) | (\d{4})-(\d{2}) 年-月 |
| 非捕獲組 | (?:...) | 僅分組不捕獲 | (?:\d{3})- |
String input = "HelloWorld";
// 貪婪 (預設) - 匹配盡可能多
Pattern.compile(".*").matcher(input).group(); // HelloWorld
// 懶惰 (? 後綴) - 匹配盡可能少
Pattern.compile(".*?").matcher(input).group(); // Hello
// 獨佔 (+ 後綴) - 不回溯,失敗即失敗
Pattern.compile(".*+").matcher(input); // 無匹配 (.*+ 吞掉所有字符導致
String text = "訂單號: ORD-20240520-001, 金額: $1,234.56";
// 1. 提取所有數字
Matcher m = Pattern.compile("\\d+").matcher(text);
while (m.find()) System.out.println(m.group()); // 20240520, 001, 1, 234, 56
// 2. 替換:隱藏金額後四位
String masked = text.replaceAll("(\\$[\\d,]+)\\.\\d{2}", "$1.**"); // $1,234.**
// 3. 使用 Matcher.appendReplacement/Tail 精細控制
StringBuilder sb = new StringBuilder();
Matcher m2 = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})").matcher("2024-05-20");
while (m2.find()) {
m2.appendReplacement(sb, "$3/$2/$1"); // 20/05/2024
}
m2.appendTail(sb);
System.out.println(sb); // 20/05/2024
Pattern.CASE_INSENSITIVE // 忽略大小寫 (?i)
Pattern.MULTILINE // ^$ 匹配行首行尾 (?m)
Pattern.DOTALL // . 匹配含換行 (?s)
Pattern.UNICODE_CHARACTER_CLASS // \w \d \s 支援 Unicode (?U)
Pattern.COMMENTS // 允許註解與空白 (?x)
Pattern.UNIX_LINES // 僅 \n 視為行終結符 (?d)
// 組合使用
Pattern.compile("(?i)(?m)^error.*$"); // 忽略大小寫、多行模式匹配以 error 開頭的行
(a+)+、(a*)* 易觸發指數級回溯。(?>...) 或 *+ 切斷回溯路徑。^ 大幅縮小搜索空間。[a-z] 替代 .,減少嘗試分支。
public final class RegexUtils {
private static final Map CACHE = new ConcurrentHashMap<>();
private static final int MAX_CACHE = 256;
public static Pattern getPattern(String regex) {
return CACHE.computeIfAbsent(regex, Pattern::compile);
}
public static boolean isMatch(String regex, String input) {
return getPattern(regex).matcher(input).matches();
}
public static boolean contains(String regex, String input) {
return getPattern(regex).matcher(input).find();
}
public static List findAll(String regex, String input) {
List list = new ArrayList<>();
Matcher m = getPattern(regex).matcher(input);
while (m.find()) list.add(m.group());
return list;
}
public static String replaceFirst(String regex, String replacement, String input) {
return getPattern(regex).matcher(input).replaceFirst(replacement);
}
public static String replaceAll(String regex, String replacement, String input) {
return getPattern(regex).matcher(input).replaceAll(replacement);
}
public static String[] split(String regex, String input, int limit) {
return getPattern(regex).split(input, limit);
}
}
熟練掌握 Java 正則 API 與優化原則,能高效解決 90% 以上的文本解析、驗證、清洗需求。