首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 正則表達式深度解析:從入門到實戰

     編輯:Java 程式設計入門

前言

正則表達式(Regular Expressions,簡稱 regex)是處理字符串的強大工具,廣泛應用於匹配、查找、替換等場景。在 Unix 和 Perl 中早已普及,Java 通過 java.util.regex 套件提供了完整的支持,核心類包括 Pattern(編譯後的正則模式)和 Matcher(匹配結果)。本文將系統梳理 Java 正則的語法、常用 API 及實戰技巧。

一、基礎入門:String 類的便捷方法

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

二、核心 API:Pattern 與 Matcher

對於複雜或重複使用的正則,建議先編譯為 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

六、POSIX 字符類(僅 US-ASCII)

語法:\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,判斷空白行

八、Matcher 進階:find、start、end、lookingAt

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

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));  // 第二組:字母部分
}

十一、實戰:抓取網頁 Email

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;注意轉義規則與性能陷阱(如過度回溯)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved