正則表達式是文本處理的瑞士軍刀。本文系統梳理字符類、量詞、邊界、分組捕獲等核心語法,實戰 Java Pattern/Matcher、String 便利方法、預編譯緩存、回溯陷阱優化,附郵箱驗證、敏感詞過濾、日誌解析三大生產案例。
| 分類 | 語法 | 含義 | 示例匹配 |
|---|---|---|---|
| 字符類 | [abc] | 單字符:a/b/c | a |
[^abc] | 除 a/b/c 外任意字符 | d | |
[a-zA-Z] | 字母範圍 | Z | |
[0-9] | 數字範圍 | 5 | |
. | 任意字符(除換行) | @ | |
\d \D \w \W \s \S | 預定義:數字/非數字/單詞/非單詞/空白/非空白 | \d→8 | |
| 邊界 | ^ $ | 行首/行尾(多行模式影響) | ^Hello |
\b \B | 單詞邊界/非邊界 | \bcat\b | |
\A \Z \z | 字符串絕對首/尾(不受多行模式影響) | \Astart | |
| 量詞 | ? | 0 或 1 次 | colou?r |
* | 0 或多次 | ab*c | |
+ | 1 或多次 | ab+c | |
{n} | 恰好 n 次 | \d{3} | |
{n,m} | n 到 m 次 | \d{2,4} | |
| 貪婪/懶惰 | *? +? ?? | 懶惰模式:盡可能少匹配 | .*?end |
*+ ++ ?+ | 佔有優先模式:不回溯 | .*+end | |
(?>...) | 原子組:禁止回溯 | (?>\d+) |
// 匹配整字符串
"abc".matches("\\w+"); // true
// 分割
"192.168.1.1".split("\\."); // [192, 168, 1, 1]
// 替換
"fuck shit".replaceAll("(fuck|shit)", "***"); // "*** ***"
// 獲取(Java 9+)
"abc123def".replaceAll("\\d+", "*"); // "abc*def"
// 1. 預編譯緩存(關鍵優化)
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[\\w.-]+@[\\w-]+(\\.[\\w-]+)+$");
// 2. Matcher 復用
Matcher matcher = EMAIL_PATTERN.matcher(input);
if (matcher.matches()) { ... }
// 3. 分組捕獲與提取
Pattern logPattern = Pattern.compile(
"(?
Pattern p = Pattern.compile("\\b\\w{3}\\b"); // 3字母單詞
Matcher m = p.matcher("You may be out of my sight");
while (m.find()) {
System.out.println(m.group() + " @ [" + m.start() + "," + m.end() + ")");
}
// 輸出: You @ [0,3) may @ [4,7) out @ [8,11) ...
private static final Pattern EMAIL = Pattern.compile(
"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+" + // local part
"@" +
"[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?" + // domain
"(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$"
);
// 測試
Stream.of("user@example.com", "user.name@sub.domain.cn", "invalid@.com")
.forEach(e -> System.out.println(e + " → " + EMAIL.matcher(e).matches()));
// 正則版(小規模詞庫)
private static final Pattern SENSITIVE = Pattern.compile(
"(?i)(fuck|shit|damn|bitch|asshole)"); // (?i) 忽略大小寫
public String filter(String text) {
return SENSITIVE.matcher(text).replaceAll(m ->
"*".repeat(m.group().length()));
}
// 大規模詞庫必須用 Trie/DFA,正則回溯會導致災難性性能
// 典型格式: 192.168.1.1 - - [10/Jan/2024:10:30:45 +0800] "GET /api HTTP/1.1" 200 1234
private static final Pattern NGINX_LOG = Pattern.compile(
"(?\\d+\\.\\d+\\.\\d+\\.\\d+) " +
".*? " +
"\\[(?
| 原則 | 錯誤示範 | 正確做法 |
|---|---|---|
| 預編譯緩存 Pattern | 循環中 Pattern.compile() | static final 常量 |
| 避免災難性回溯 | (.*)* (a+)+ | 用 .*?、(?>...)、具體字符類 |
| 優先具體字符類 | .* 匹配非引號內容 | [^"]* 零回溯 |
| 錨定開頭結尾 | \\d+ 匹配整行數字 | ^\\d+$ 快速失敗 |
| 複用 Matcher | 每次 pattern.matcher(s) | matcher.reset(newInput) |
| 分組命名而非索引 | m.group(1) | m.group("ip") 可維護 |
// Matcher 結果流
Pattern.compile("\\d+").matcher("a1b22c333")
.results()
.map(MatchResult::group)
.forEach(System.out::println); // 1, 22, 333
// 不可變 Pattern(線程安全,可直接共享)
Pattern p = Pattern.compile("\\d+");
正則表達式寫時只讀,維護地獄。生產環境三原則:預編譯緩存、具體字符類消除回溯、命名分組提升可讀性。複雜文本解析優先考慮專用解析器(JSON/CSV/Log4j),正則僅作輕量提取。