正則表達式是文本處理的瑞士軍刀。Java 通過 java.util.regex 包(Pattern、Matcher)提供強大支持。本文系統講解核心語法、String 常用方法、Pattern/Matcher 進階用法,並以「網頁郵箱爬蟲」為實戰案例,展示從網絡流讀取、正則匹配、文件持久化的完整鏈路。
| 元字符 | 含義 | 示例 |
|---|---|---|
. | 任意單字符(除換行) | a.c 匹配 "abc", "a@c" |
\d | 數字 [0-9] | \d{3} 三位數 |
\D | 非數字 | |
\w | 單詞字符 [a-zA-Z0-9_] | \w+ 匹配 "user_123" |
\W | 非單詞字符 | |
\s | 空白字符(空格、制表、換行) | |
\S | 非空白字符 | |
[] | 字符集合 | [aeiou] 元音 |
[^] | 排除字符集合 | [^0-9] 非數字 |
* | 0 次或多次(貪婪) | ab*c 匹配 "ac", "abc", "abbc" |
+ | 1 次或多次 | \d+ 至少一位數字 |
? | 0 次或 1 次 | colou?r 匹配 "color" "colour" |
{n} | 恰好 n 次 | \d{4} 四位年份 |
{n,} | 至少 n 次 | \d{2,} 至少兩位數 |
{n,m} | n 到 m 次 | [1-9]\d{4,14} QQ 號 |
() | 分組/捕獲 | (\d{4})-(\d{2}) 年-月 |
| | 或 | cat|dog |
^ | 行首 | ^https:// |
$ | 行尾 | \.jpg$ |
\b | 單詞邊界 | \bJava\b |
String regex = "[1-9]\\d{4,14}"; // QQ 號:首位非0,5-15位
"12345".matches(regex); // true
"1234".matches(regex); // false(長度不足)
String[] parts = "a,b;c|d".split("[,;|]"); // [a, b, c, d]
String masked = "phone: 13812345678".replaceAll("\\d{11}", "***********");
// "phone: ***********"
// 分組引用替換:將 yyyy-MM-dd 轉為 MM/dd/yyyy
"2024-01-15".replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$2/$3/$1");
// "01/15/2024"
// 忽略大小寫、多行模式、Unix行尾
Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher(input);
m.matches(); // 整串完全匹配
m.lookingAt(); // 從開頭匹配
m.find(); // 掃描查找下一個匹配(最常用)
String html = "<a href=\"https://example.com\">Link</a>";
Pattern p = Pattern.compile("href=\"([^\"]+)\"");
Matcher m = p.matcher(html);
while (m.find()) {
System.out.println("Group 0 (整匹配): " + m.group(0)); // href="..."
System.out.println("Group 1 (第一分組): " + m.group(1)); // https://example.com
System.out.println("Start: " + m.start(1) + ", End: " + m.end(1));
}
// 簡單替換
String r = m.replaceAll("***");
// 函數式替換(Java 9+)
String r = m.replaceAll(mr -> "[" + mr.group(1).toUpperCase() + "]");
從指定 URL 下載 HTML,提取所有符合郵箱格式的字符串,去重後寫入文件,每行一個。
// RFC 5322 簡化版:本地部分@域名
// 本地:字母數字._%+- 1-64字符
// 域名:標籤由字母數字-組成,標籤間用.分隔,頂級域至少2字母
String EMAIL_REGEX = "\\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}\\b";
// \b 單詞邊界防止匹配到 "abc@def.com.xyz" 中的 "def.com"
Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX, Pattern.CASE_INSENSITIVE);
package com.example.crawler;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EmailCrawler {
private static final String TARGET_URL = "https://example.com/forum/page.html";
private static final String OUTPUT_FILE = "emails.txt";
private static final Pattern EMAIL_PATTERN = Pattern.compile(
"\\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}\\b",
Pattern.CASE_INSENSITIVE
);
public static void main(String[] args) {
Set uniqueEmails = new LinkedHashSet<>(); // 保持順序去重
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(fetchContent(TARGET_URL), StandardCharsets.UTF_8));
BufferedWriter writer = Files.newBufferedWriter(Paths.get(OUTPUT_FILE))) {
String line;
while ((line = reader.readLine()) != null) {
Matcher m = EMAIL_PATTERN.matcher(line);
while (m.find()) {
String email = m.group().toLowerCase();
if (uniqueEmails.add(email)) { // 去重
writer.write(email);
writer.newLine();
}
}
}
writer.flush();
System.out.println("提取完成,共 " + uniqueEmails.size() + " 個唯一郵箱。");
} catch (IOException e) {
System.err.println("爬蟲異常: " + e.getMessage());
e.printStackTrace();
}
}
private static InputStream fetchContent(String urlStr) throws IOException {
URL url = new URL(urlStr);
URLConnection conn = url.openConnection();
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; EmailBot/1.0)");
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
return conn.getInputStream();
}
}
| 問題 | 原因 | 解決 |
|---|---|---|
| 正則性能災難(ReDoS) | 嵌套量詞 (a+)+、回溯爆炸 | 使用佔有優先量詞 ++、原子組 (?>...)、避免嵌套量詞、設置匹配超時 |
| 預編譯 Pattern 重複創建 | 循環中 Pattern.compile() | 靜態常量預編譯,線程安全可共享 |
| 匹配跨行文本失效 | . 不匹配換行 | 啟用 Pattern.DOTALL 或用 [\\s\\S] |
| 轉義字符混淆 | Java 字符串與正則雙重轉義 | Java 11+ 使用 \"\"\"...\"\"\" 文本塊;或工具自動轉義 |
| 郵箱正則過嚴/過鬆 | RFC 5322 極其複雜 | 業務場景通常用簡化版;極高準確度需求用專用庫 |
正則表達式是文本處理的利器,但可讀性與性能往往對立。工程實踐原則:
startsWith, contains, split)解決的不用正則。Pattern 對象。Matcher.find() 掃描,配合分組提取。掌握這些核心模式,你就能應對 90% 的文本解析與提取需求。