正則表達式是文本處理的「瑞士軍刀」。Java 1.4 前無原生支持,開發者依賴 Jakarta-ORO 等第三方庫;Java 1.4+ 引入 java.util.regex 套件,提供標準 API。本文涵蓋正則語法核心、Java 原生 API 實戰、經典應用場景(日誌解析、HTML 處理、鏈接重寫),並對比新舊 API 差異。
| 元字符 | 名稱 | 含義 | 示例匹配 |
|---|---|---|---|
. | 句點 | 任意單字符(除換行符) | t.n → tan, ten, tin, ton |
[abc] | 字符類 | 方括號內任一字符 | t[aeio]n → tan, ten, tin, ton |
[^abc] | 否定字符類 | 除方括號外任一字符 | t[^aeio]n → tbn, tcn... |
\d | 數字簡寫 | [0-9] | |
\D | 非數字 | [^0-9] | |
\w | 單詞字符 | [a-zA-Z0-9_] | |
\W | 非單詞字符 | [^a-zA-Z0-9_] | |
\s | 空白字符 | 空格、Tab、換行、換頁 | |
\S | 非空白字符 | ||
| | 或運算 | 左右任一分支匹配 | cat|dog → cat 或 dog |
() | 分組/捕獲 | 提取子串、優先級控制 | (\d{4})-(\d{2}) 捕獲年月 |
(?:) | 非捕獲組 | 僅分組不捕獲 | (?:\d{3}-){2}\d{4} |
* | 零或多次 | {0,} | ab*c → ac, abc, abbc... |
+ | 一次或多次 | {1,} | ab+c → abc, abbc... |
? | 零或一次 | {0,1} | ab?c → ac, abc |
{n} | 精確 n 次 | \d{4} → 4 位數字 | |
{n,} | 至少 n 次 | \d{2,} → 2 位以上數字 | |
{n,m} | n 到 m 次 | \d{3,5} → 3~5 位數字 | |
^ | 行首/字符串首 | 多行模式下匹配行首 | ^Start |
$ | 行尾/字符串尾 | 多行模式下匹配行尾 | End$ |
\b | 單詞邊界 | \bcat\b 匹配單詞 cat | |
\B | 非單詞邊界 |
import java.util.regex.Pattern;
import java.util.regex.Matcher;
// 1. 編譯模式(線程安全,可復用)
Pattern pattern = Pattern.compile(regex); // 大小寫敏感
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); // 忽略大小寫
// 2. 創建匹配器(非線程安全,每次匹配 new)
Matcher matcher = pattern.matcher(inputString);
// 3. 核心匹配方法
boolean matches() // 整串精確匹配
boolean lookingAt() // 前綴匹配
boolean find() // 子串搜尋(可重複調用遍歷所有匹配)
boolean find(int start) // 從指定位置搜尋
// 4. 組提取
String group() // 整個匹配
String group(int i) // 第 i 組 (1-based)
int groupCount() // 組數量
// 5. 替換操作
String replaceAll(String replacement) // 替換所有匹配
String replaceFirst(String replacement) // 替換首個匹配
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb); // 獲取完整結果 sb.toString()
// 6. 常用靜態工具方法
Pattern.matches(regex, input) // 一行代碼精確匹配
String replaced = input.replaceAll(regex, replacement) // String 便利方法
日誌格式:192.168.1.100 - - [29/Sep/2001:18:50:09 +0800] "GET /index.html HTTP/1.1" 200 1234
String logRegex = "(\\d{1,3}(?:\\.\\d{1,3}){3})\\s-\\s-\\s\\[([^\\]]+)\\]";
// 組1: IP地址 組2: 時間戳
Pattern p = Pattern.compile(logRegex);
Matcher m = p.matcher(logLine);
if (m.find()) {
String ip = m.group(1);
String timestamp = m.group(2);
// 處理業務邏輯...
}
// 兩步法:先提取標籤整體屬性串,再拆分名值對
String fontTagRegex = "<font\\s+([^>]+)>"; // 組1: 所有屬性
String attrRegex = "(\\w+)\\s*=\\s*\"([^\"]*)\""; // 組1:名 組2:值
Pattern tagPattern = Pattern.compile(fontTagRegex, Pattern.CASE_INSENSITIVE);
Pattern attrPattern = Pattern.compile(attrRegex);
Matcher tagMatcher = tagPattern.matcher(htmlLine);
while (tagMatcher.find()) {
String attrs = tagMatcher.group(1); // face="Arial" size="+2" color="red"
Matcher attrMatcher = attrPattern.matcher(attrs);
while (attrMatcher.find()) {
System.out.println(attrMatcher.group(1) + " = " + attrMatcher.group(2));
}
}
將 href="https://widgets.acme.com/..." 改為 href="https://newserver.acme.com/..."
String linkRegex = "(href\\s*=\\s*\")https://widgets\\.acme\\.com(.*?\")";
// 組1: 前綴 組2: 後綴
String replacement = "$1https://newserver.acme.com$2";
String result = input.replaceAll(linkRegex, replacement);
// $1 $2 為組引用,對應 replacement 中的 $1 $2
| 維度 | Jakarta-ORO (舊) | java.util.regex (標準) |
|---|---|---|
| 包名 | com.oroinc.text.regex.* | java.util.regex.* |
| 編譯器 | PatternCompiler + Perl5Compiler | Pattern.compile() |
| 模式對象 | Pattern (接口) | Pattern (final 類) |
| 匹配器 | PatternMatcher + Perl5Matcher | Matcher |
| 匹配方法 | matches()、contains() | matches()、find() |
| 組提取 | MatchResult.getGroup(i) | matcher.group(i) |
| 替換 | Util.substitute() + Perl5Substitution | matcher.replaceAll() / String.replaceAll() |
| Perl 5 兼容性 | 完全兼容 | 高度兼容(少數高級特性差異) |
| 性能 | 優化較好 | JDK 內建,持續優化 |
| 依賴 | 需引入第三方 JAR | 零依賴,JDK 內建 |
// ORO 寫法
PatternCompiler compiler = new Perl5Compiler();
Pattern pattern = compiler.compile(regex, Perl5Compiler.CASE_INSENSITIVE_MASK);
PatternMatcher matcher = new Perl5Matcher();
if (matcher.contains(input, pattern)) {
MatchResult result = matcher.getMatch();
String group1 = result.getGroup(1);
}
// 標準 API 寫法(推薦)
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
String group1 = matcher.group(1);
}
Pattern 線程安全,作為常量/靜態字段緩存(?:...) 非捕獲組^ $ \b 減少回溯.*? 非貪婪避免過度匹配,但增加回溯開銷,按需選擇Pattern.CASE_INSENSITIVE、Pattern.MULTILINE、Pattern.DOTALL(讓 . 匹配換行)| 陷阱 | 原因 | 修正 |
|---|---|---|
| Java 字符串雙重轉義 | "\\d+" → 正則引擎見 \d+ | 習慣雙寫反斜杠;Java 13+ 可用文本塊 """\\d+""" |
matches() 需整串匹配 | Pattern.matches("\\d+", "abc123") 返回 false | 用 matcher.find() 或 ".*\\d+.*" |
| 分組索引從 1 開始 | group(0) 為整匹配 | 切記 group(1) 是首個捕獲組 |
替換中 $ 需轉義 | replaceAll(".", "$1") 會按組引用解析 | 用 Matcher.quoteReplacement() 或雙寫 \\$ |
總結:正則表達式威力巨大,但「寫時易讀,讀時難寫」。掌握核心語法、善用 Pattern/Matcher 標準 API、建立常用正則片段庫(郵箱、手機、身份證、URL、日期等),並遵循「預編譯、非捕獲、錨定、拆解」原則,能高效解決 90% 文本處理需求。現代 Java 專案棄用 Jakarta-ORO,全面擁抱 java.util.regex。