正则表达式
1. 基本结构¶
Java通过java.util.regex包中的Pattern和Matcher类实现正则表达式:
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}匹配任意语言字母。
常见用例¶
- 邮箱验证:
- 日期匹配(YYYY-MM-DD):
-
贪婪 vs 勉强模式:
-
默认贪婪(尽可能多匹配),如
.*。 - 添加
?变为勉强模式(尽可能少匹配),如.*?。
注意事项¶
- 转义问题: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. 贪婪、勉强与独占量词¶
- 贪婪模式(默认):尽可能多匹配。
- 勉强模式(在量词后加
?):尽可能少匹配。
- 独占模式(在量词后加
+,Java特有):尽可能多匹配,且不回溯。
13. 复杂模式示例¶
- 匹配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"
}
- 提取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. 正则表达式调试工具¶
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
System.out.println("Found: " + matcher.group() + " at position " + matcher.start());
}
16. 常见错误与解决方案¶
- 转义错误:
- 错误:
String regex = "\d";(应为\\d)。 - 修正:在Java字符串中,反斜杠需转义为
\\。 - 边界条件遗漏:
- 错误:使用
.*匹配任意内容时,可能意外包含换行符。 - 修正:使用
Pattern.DOTALL标志使.匹配换行符。 - 过度复杂化:
- 错误:用正则解析嵌套结构(如JSON/XML)。
- 修正:优先使用专用解析库(如Jackson/DOM)。
17. 扩展:Unicode与多语言支持¶
-
匹配Unicode字符:
-
\p{L}:匹配任意语言的字母(包括中文、日文等)。 \p{IsHan}:匹配汉字。
示例:
String regex = "\\p{L}+"; // 匹配任意语言的字母序列
System.out.println("你好Hello".matches(regex)); // 输出 false(含空格需调整)
- 启用Unicode模式:
总结¶
掌握Java正则表达式的核心在于:
- 理解元字符和转义规则。
- 熟练使用量词、分组和边界控制。
- 优化性能,避免常见陷阱。
- 结合工具调试和验证表达式。
通过实际项目中的练习(如数据清洗、表单验证),可以逐步提升正则表达式的应用能力。