跳转至

正则表达式

1. 基本结构

Java通过java.util.regex包中的PatternMatcher类实现正则表达式:

Pattern pattern = Pattern.compile("正则表达式");
Matcher matcher = pattern.matcher("待匹配字符串");
boolean isMatch = matcher.matches(); // 完全匹配

2. 普通字符和元字符

  • 普通字符:直接匹配自身,如 abc 匹配字符串中的 "abc"。
  • 元字符(需转义的特殊字符):. * + ? ^ $ \ | ( ) [ ] { }
  • 在Java字符串中需用双反斜杠转义,例如 \\d 匹配数字。
  • .表示除了换行符之外的任意字符

3. 字符类

  • 简单字符类[abc] 匹配 a、b 或 c。
  • 范围字符类[a-z] 匹配任意小写字母。
  • 否定字符类[^abc] 匹配非 a、b、c 的字符。
  • 特殊字符处理[-^] 匹配 -^[\\]] 匹配 ]

4. 预定义字符类

  • \d:数字,等价于 [0-9]
  • \D:非数字,等价于 [^0-9]
  • \s:空白字符(空格、制表符、换行等)。
  • \S:非空白字符。
  • \w:单词字符(字母、数字、下划线),等价于 [a-zA-Z0-9_]
  • \W:非单词字符。

5. 量词(匹配次数)

  • X?:0 或 1 次。
  • X+:1 次或多次。
  • X*:0 次或多次。
  • X{n}:恰好 n 次。
  • X{n,}:至少 n 次。
  • X{n,m}:n 到 m 次。

示例a{3} 匹配 "aaa",a{2,4} 匹配 "aa"、"aaa" 或 "aaaa"。


6. 边界匹配

  • ^:行开头。
  • $:行结尾。
  • \b:单词边界(如 \bcat\b 匹配独立的单词 "cat")。
  • \B:非单词边界。

7. 分组与捕获

  • 分组:用 () 包裹,如 (ab)+ 匹配 "abab"。
  • 捕获组:通过索引引用,例如 (\\d\\d)\\1 匹配 "1212"(\1 表示第一个分组内容)。
  • 非捕获组(?:...) 仅分组不捕获(如 (?:ab)+)。

8. 特殊构造

  • 前瞻/后顾
  • (?=...):肯定前瞻(匹配后面是某模式的位置)。
  • (?!...):否定前瞻。
  • (?<=...):肯定后顾(匹配前面是某模式的位置)。
  • (?<!...):否定后顾。

示例Windows(?=95|98) 匹配 "Windows" 仅当其后面是 "95" 或 "98"。


9. 在Java中的使用

  • 字符串方法
boolean isMatch = "abc123".matches(".*\\d+.*"); // 检查是否包含数字
String[] parts = "a,b,c".split("\\,");          // 分割字符串
String result = "123".replaceAll("\\d", "#");   // 替换所有数字为 #
  • 分组替换
String phone = "1234567890";
String formatted = phone.replaceAll("(\\d{3})(\\d{3})(\\d{4})", "($1) $2-$3");
// 输出:(123) 456-7890

10. 标志与高级选项

  • 不区分大小写Pattern.compile("regex", Pattern.CASE_INSENSITIVE)
  • 多行模式Pattern.MULTILINE(使 ^$ 匹配每行的开头/结尾)。
  • Unicode支持Pattern.UNICODE_CASE 结合 \p{L} 匹配任意语言字母。

常见用例

  1. 邮箱验证
String emailRegex = "^[\\w.-]+@[\\w.-]+\\.\\w{2,}$";
  1. 日期匹配(YYYY-MM-DD)
String dateRegex = "\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])";
  1. 贪婪 vs 勉强模式

  2. 默认贪婪(尽可能多匹配),如 .*

  3. 添加 ? 变为勉强模式(尽可能少匹配),如 .*?

注意事项

  • 转义问题:Java字符串中需用双反斜杠表示正则中的单反斜杠(如 \\d)。
  • 性能:复杂正则可能导致性能问题,需优化表达式。

通过理解和练习这些语法规则,可以高效利用正则表达式处理文本任务。

11. 反向引用与命名分组

在捕获组中,可以通过索引或名称引用已匹配的内容。

  • 反向引用
// 匹配重复的单词,如 "the the"
String regex = "(\\b\\w+\\b)\\s+\\1";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher("the the");
System.out.println(matcher.find()); // 输出 true
  • 命名分组(Java 7+支持): 使用 (?<name>...) 定义命名分组,并通过 \k<name>Matcher.group("name") 引用:
String regex = "(?<year>\\d{4})-(?<month>\\d{2})";
Matcher matcher = Pattern.compile(regex).matcher("2023-10");
if (matcher.find()) {
    System.out.println(matcher.group("year"));  // 输出 2023
    System.out.println(matcher.group("month")); // 输出 10
}

12. 贪婪、勉强与独占量词

  • 贪婪模式(默认):尽可能多匹配。
// 匹配 "aab" 中的 "aab"
"aab".matches("a.*b"); 
  • 勉强模式(在量词后加 ?):尽可能少匹配。
// 匹配 "aab" 中的 "ab"
"aab".matches("a.*?b"); 
  • 独占模式(在量词后加 +,Java特有):尽可能多匹配,且不回溯。
// 匹配失败(独占模式不回溯)
"aab".matches("a.++b"); 

13. 复杂模式示例

  1. 匹配HTML标签内容(避免贪婪匹配):
String html = "<div>content</div>";
String regex = "<div>(.*?)</div>";
Matcher matcher = Pattern.compile(regex).matcher(html);
if (matcher.find()) {
    System.out.println(matcher.group(1)); // 输出 "content"
}
  1. 提取URL参数
String url = "https://example.com?name=John&age=30";
String regex = "[?&]([^&=]+)=([^&]*)";
Matcher matcher = Pattern.compile(regex).matcher(url);
while (matcher.find()) {
    System.out.println("Key: " + matcher.group(1) + ", Value: " + matcher.group(2));
}
// 输出:
// Key: name, Value: John
// Key: age, Value: 30

14. 性能优化与陷阱

  • 避免过度回溯: 复杂正则可能导致性能问题。例如,表达式 (a+)+b 匹配 aaaaaaaaac 时会大量回溯。 优化方法:使用独占量词(a++)或限制重复次数。
  • 预编译正则表达式: 频繁使用的正则表达式应通过 Pattern.compile() 预编译,避免重复解析。
  • 避免不必要的捕获组: 使用 (?:...) 替代 () 减少内存开销。

15. 正则表达式调试工具

  • 在线工具

  • Regex101:支持Java语法,提供详细解释和匹配过程可视化。

  • RegExr:实时测试正则表达式。

  • Java代码调试

Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
    System.out.println("Found: " + matcher.group() + " at position " + matcher.start());
}

16. 常见错误与解决方案

  1. 转义错误
  2. 错误String regex = "\d";(应为 \\d)。
  3. 修正:在Java字符串中,反斜杠需转义为 \\
  4. 边界条件遗漏
  5. 错误:使用 .* 匹配任意内容时,可能意外包含换行符。
  6. 修正:使用 Pattern.DOTALL 标志使 . 匹配换行符。
  7. 过度复杂化
  8. 错误:用正则解析嵌套结构(如JSON/XML)。
  9. 修正:优先使用专用解析库(如Jackson/DOM)。

17. 扩展:Unicode与多语言支持

  • 匹配Unicode字符

  • \p{L}:匹配任意语言的字母(包括中文、日文等)。

  • \p{IsHan}:匹配汉字。

示例

String regex = "\\p{L}+"; // 匹配任意语言的字母序列
System.out.println("你好Hello".matches(regex)); // 输出 false(含空格需调整)
  • 启用Unicode模式
Pattern pattern = Pattern.compile("regex", Pattern.UNICODE_CHARACTER_CLASS);

总结

掌握Java正则表达式的核心在于:

  1. 理解元字符和转义规则
  2. 熟练使用量词、分组和边界控制
  3. 优化性能,避免常见陷阱
  4. 结合工具调试和验证表达式

通过实际项目中的练习(如数据清洗、表单验证),可以逐步提升正则表达式的应用能力。