正則表達式(Regular Expressions,簡稱 regex)是處理字符串的強大工具,廣泛應用於匹配、查找、替換等場景。在 Unix 和 Perl 中早已普及,Java 通過 java.util.regex 套件提供了完整的支持,核心類包括 Pattern(編譯後的正則模式)和 Matcher(匹配結果)。本文將系統梳理 Java 正則的語法、常用 API 及實戰技巧。
Java 的 String 類內建了幾個直接使用正則的方法,適合簡單場景:
matches(String regex):判斷整個字符串是否匹配給定正則。replaceAll(String regex, String replacement):將所有匹配的子串替換為指定字符串。split(String regex):按正則分割字符串(未在原文展示但常用)。// 每個 "." 代表任意單字符
System.out.println("abc".matches("...")); // true
// 將所有數字替換為 "-"
System.out.println("ab54564654sbg48746bshj".replaceAll("[0-9]", "-"));
// 輸出: ab--------sbg----bshj
對於複雜或重複使用的正則,建議先編譯為 Pattern,再獲取 Matcher 進行操作,效率更高且功能更豐富。
| 語法 | 含義 |
|---|---|
| X{n} | 恰好 n 次 |
| X{n,} | 至少 n 次 |
| X{n,m} | 至少 n 次,不超過 m 次 |
Pattern p = Pattern.compile("[a-z]{3}"); // 編譯模式:恰好 3 個小寫字母
Matcher m = p.matcher("ggs");
System.out.println(m.matches()); // true
// 若需多次匹配,可重用 Pattern
// m.reset() 可重置匹配器狀態
在正則中,.、*、+、?、{}、[]、()、|、^、$、\ 等具有特殊含義,若需匹配字面字符需轉義。Java 字符串中反斜桿需雙寫(\\\\ 代表正則中的 \\)。
"a".matches("."); // true,任意單字符
"aa".matches("aa"); // true,普通字符串亦可作正則
"aaaa".matches("a*"); // true,* 表示 0 或多個 'a'
"".matches("a*"); // true,0 個 'a' 也匹配
"aaa".matches("a?"); // true,? 表示 0 或 1 個
"544848154564113".matches("\\d{3,100}"); // true,3 到 100 位數字
// 簡易 IP 匹配(不驗證 0-255 範圍)
"192.168.0.aaa".matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"); // false,最後一段非數字
方括號 [] 定義字符集合:
[abc]:a、b 或 c[^abc]:非 a、b、c(注意:空串不匹配)[a-zA-Z]:字母範圍[A-Z&&[ABS]]:交集(大寫字母中取 A、B、S)System.out.println("C".matches("[A-Z&&[ABS]]")); // false
System.out.println("C".matches("[A-Z&[ABS]]")); // true(單 & 視為字面字符?實為並集行為)
System.out.println("A".matches("[A-Z&&[ABS]]")); // true
System.out.println("C".matches("[A-Z|[ABS]]")); // true(| 在 [] 內視為字面字符)
| 簡寫 | 等價類 | 說明 |
|---|---|---|
| \d | [0-9] | 數字 |
| \D | [^0-9] | 非數字 |
| \s | [ \t\n\x0B\f\r] | 空白字符 |
| \S | [^\s] | 非空白 |
| \w | [a-zA-Z_0-9] | 單詞字符(字母、數字、下劃線) |
| \W | [^\w] | 非單詞字符 |
" \n\t\r".matches("\\s{4}"); // true,4 個空白字符
" ".matches("\\S"); // false,空格非非空白
"a_8".matches("\\w{3}"); // true
"abc888&^%".matches("[a-z]{1,3}\\d+[&^#%]+"); // true
// 匹配單個反斜桿:正則為 \\\\,Java 字符串寫 "\\\\\\\\"
System.out.println("\\".matches("\\\\")); // true
語法:\p{Name},常用如下:
\p{Lower}:[a-z]\p{Upper}:[A-Z]\p{ASCII}:[\x00-\x7F]\p{Alpha}:[\p{Lower}\p{Upper}]\p{Digit}:[0-9]| 符號 | 含義 |
|---|---|
| ^ | 行首(多行模式下) |
| $ | 行尾(多行模式下) |
| \b | 單詞邊界 |
| \B | 非單詞邊界 |
| \A | 輸入開頭 |
| \G | 上一匹配結尾 |
| \Z | 輸入結尾(允許末尾換行) |
| \z | 輸入絕對結尾 |
"hello world".matches("^h.*"); // true
"hello world".matches(".*ld$"); // true
"hello world".matches("^h[a-z]{1,3}o\\b.*"); // true,\b 匹配 'o' 後的單詞邊界
"helloworld".matches("^h[a-z]{1,3}o\\b.*"); // false,無單詞邊界
" \n".matches("^[\\s&&[^\\n]]*\\n$"); // true,判斷空白行
find() 尋找下一個匹配的子序列,配合 start()、end() 可獲取位置。注意 matches() 嘗試匹配整個輸入,會消耗字符;若混用 find() 需調用 reset() 重置。
Pattern p = Pattern.compile("\\d{3,5}");
String s = "133-34444-333-00";
Matcher m = p.matcher(s);
m.matches(); // 嘗試匹配整串,失敗於第一個 '-'
m.reset(); // 重置,歸還已消耗字符
m.find(); // 133 (0-3)
m.find(); // 34444 (4-9)
m.find(); // 333 (10-13)
m.find(); // 00 (14-16) -> 只有兩位,不匹配,返回 false
// lookingAt() 總是從區域開頭嘗試匹配,不消耗字符
m.lookingAt(); // 多次調用結果相同
replaceAll 可一次性替換所有匹配;若需根據匹配次數動態替換,可使用 appendReplacement 和 appendTail。
Pattern p = Pattern.compile("java", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher("Java java hxsyl Ilovejava java JaVaAcmer");
// 簡單替換全部為 "Java"
String s = m.replaceAll("Java");
System.out.println(s); // Java Java hxsyl IloveJava Java JaVaAcmer
// 進階:奇數次替換為 "Java",偶數次為 "java"
m.reset();
StringBuffer sb = new StringBuffer();
int i = 0;
while (m.find()) {
i++;
if ((i & 1) == 1) m.appendReplacement(sb, "Java");
else m.appendReplacement(sb, "java");
}
m.appendTail(sb);
System.out.println(sb); // Java java hxsyl IloveJava java JaVaAcmer
括號 () 定義捕獲組,編號從左到右按左括號順序。group(0) 為整個匹配,group(1) 為第一組,以此類推。替換或正則中可用 \1、\2 引用。
Pattern p = Pattern.compile("(\\d{3,5})([a-z]{2})");
String s = "123aaa-77878bb-646dd-00";
Matcher m = p.matcher(s);
while (m.find()) {
System.out.println(m.group()); // 整個匹配,如 123aaa
System.out.println(m.group(1)); // 第一組:數字部分
System.out.println(m.group(2)); // 第二組:字母部分
}
Pattern p = Pattern.compile("[\\w[.-]]+@[\\w[.-]]+\\.[\\w]+");
Matcher m = p.matcher(line);
while (m.find()) {
System.out.println(m.group());
}
利用正則判斷空行、註釋行、代碼行,結合 File 遍歷目錄。
X*、X+、X? —— 盡可能多匹配,失敗則回溯。X*?、X+?、X?? —— 盡可能少匹配,逐步增加。X*+、X++、X?+ —— 盡可能多匹配,不回溯,性能最佳但可能導致匹配失敗。// 貪婪:先吞 10 個字符,回溯至匹配
Pattern.compile("(.{3,10})+[0-9]").matcher("aaaa5bbbb6").find(); // 匹配 0-10
// 勉強:先吞 3 個,逐步擴展
Pattern.compile("(.{3,10}?)+[0-9]").matcher("aaaa5bbbb6").find(); // 匹配 0-5
// 獨占:吞 10 個不回溯,直接失敗
Pattern.compile("(.{3,10}++)+[0-9]").matcher("aaaa5bbbb6").find(); // 不匹配
(?:X):非捕獲組,不佔用組編號。(?=X):零寬正向先行斷言(lookahead),要求後面匹配 X,但不消耗字符。(?!X):零寬負向先行斷言,要求後面不匹配 X。(?<=X):零寬正向後發斷言(lookbehind),要求前面匹配 X。(?<!X):零寬負向後發斷言,要求前面不匹配 X。(?>X):獨立非捕獲組(原子組),匹配後不回溯。// 要求以 'a' 開頭,匹配 3 個字符
Pattern.compile("(?=a).{3}").matcher("444a66b").find(); // 匹配 "a66"
// 要求不以 'a' 結尾(負向後發)
Pattern.compile(".{3}(?<!a)").matcher("444a66b"); // 匹配 "444", "66b"
在正則內部使用 (?flags) 控制匹配模式,常用:
(?i):忽略大小寫(等同 Pattern.CASE_INSENSITIVE)(?m):多行模式(^/$ 匹配行首尾)(?s):單行模式(. 匹配換行)(?x):註釋模式(忽略空白,# 後為註釋)亦可組合:(?im)、(?i:abc) 僅對 abc 啟用忽略大小寫。
Java 正則表達式功能強大,掌握 Pattern/Matcher 核心 API、常用字符類、量詞模式、分組與斷言,即可應對絕大多數文本處理需求。實戰中建議:優先使用預編譯 Pattern;善用 find() 迭代匹配;複雜替換用 appendReplacement;注意轉義規則與性能陷阱(如過度回溯)。