Java 正则表达式
·
Java 正则表达式学习笔记(详细版)
正则表达式(Regular Expression,简称 Regex)是一种强大的文本模式匹配工具。在 Java 中,正则表达式通过 java.util.regex 包实现,主要用于字符串匹配、查找、替换和分割。
一、核心类与流程
Java 处理正则表达式主要涉及三个类:
Pattern:正则表达式的编译表示。- 正则表达式字符串首先被编译成
Pattern对象。 - 最佳实践:如果正则表达式会被重复使用,应将其编译为
Pattern对象并缓存(static final),避免重复编译带来的性能开销。
- 正则表达式字符串首先被编译成
Matcher:执行匹配操作的对象。- 由
Pattern对象创建,用于对目标字符串进行匹配、查找和替换。
- 由
PatternSyntaxException:当正则表达式语法错误时抛出的异常。
标准使用流程
// 1. 编译正则表达式
Pattern pattern = Pattern.compile("\\d+");
// 2. 创建匹配器
Matcher matcher = pattern.matcher("abc123def456");
// 3. 执行匹配操作
while (matcher.find()) {
System.out.println("找到: " + matcher.group());
}
二、常用元字符与语法
在 Java 字符串中,反斜杠 \ 是转义字符,因此正则表达式中的 \ 需要写成 \\。
1. 字符匹配
| 符号 | 含义 | 示例 | 匹配内容 |
|---|---|---|---|
. | 匹配任意单个字符(除换行符外) | a.c | abc, a2c |
\d | 匹配数字 | \d+ | 123, 0 |
\D | 匹配非数字 | \D+ | abc, ! |
\w | 匹配单词字符(字母、数字、下划线) | \w+ | user_name, a1 |
\W | 匹配非单词字符 | \W+ | !@#, |
\s | 匹配空白字符(空格、制表符、换行) | \s+ | , \t |
\S | 匹配非空白字符 | \S+ | abc |
[abc] | 匹配括号内的任意一个字符 | [a-z] | a, z |
[^abc] | 匹配非括号内的字符 | [^0-9] | a, ! |
[a-z] | 匹配范围内的字符 | [a-zA-Z] | a, Z |
2. 数量限定符
| 符号 | 含义 | 示例 | 匹配次数 |
|---|---|---|---|
* | 0 次或多次 | ab*c | ac, abc, abbbc |
+ | 1 次或多次 | ab+c | abc, abbbc (不匹配 ac) |
? | 0 次或 1 次 | colou?r | color, colour |
{n} | 恰好 n 次 | \d{3} | 123 |
{n,} | 至少 n 次 | \d{3,} | 123, 12345 |
{n,m} | n 到 m 次 | \d{2,4} | 12, 1234 |
注意:默认是贪婪匹配(尽可能多匹配)。
*是贪婪的,*?是非贪婪(懒惰)匹配。- 示例:
a.*b匹配aabab会匹配整个字符串;a.*?b会匹配aab和ab。
3. 边界匹配
| 符号 | 含义 | 示例 |
|---|---|---|
^ | 匹配字符串开头 | ^abc (以 abc 开头) |
$ | 匹配字符串结尾 | xyz$ (以 xyz 结尾) |
\b | 匹配单词边界 | \bcat\b (匹配独立的 cat) |
\B | 匹配非单词边界 | \Bcat\B |
4. 分组与引用
():捕获分组。将括号内的内容作为一个整体,并可以后续引用。(?:...):非捕获分组。只分组,不捕获(不占用组号,性能稍好)。\1,\2:反向引用。引用前面第 1、2 个分组匹配的内容。|:或。匹配左边或右边。
示例:
// 匹配重复的单词
String regex = "(\\w+)\\s+\\1";
// 匹配 "hello hello",但不匹配 "hello world"
三、常用方法详解
1. Pattern 类的方法
static Pattern compile(String regex):编译正则。static Pattern compile(String regex, int flags):带标志位编译(如CASE_INSENSITIVE忽略大小写)。Matcher matcher(CharSequence input):创建匹配器。
2. Matcher 类的方法
| 方法 | 功能 | 返回值 | 说明 |
|---|---|---|---|
matches() | 判断整个字符串是否匹配 | boolean | 必须完全匹配 |
lookingAt() | 判断开头是否匹配 | boolean | 只要开头匹配即可 |
find() | 查找下一个匹配项 | boolean | 可循环调用查找所有 |
group() | 返回当前匹配的文本 | String | 默认返回第 0 组(整体) |
group(int i) | 返回第 i 个捕获组 | String | 从 1 开始计数 |
start() / end() | 返回匹配项的起始/结束索引 | int | |
replaceAll(String repl) | 替换所有匹配项 | String | 返回新字符串 |
replaceFirst(String repl) | 替换第一个匹配项 | String | |
appendReplacement() / appendTail() | 高级替换(支持动态逻辑) | Matcher | 用于复杂替换场景 |
四、实战代码示例
1. 验证邮箱格式
public static boolean isValidEmail(String email) {
// 简单邮箱正则:字母数字下划线 + @ + 域名 + . + 后缀
String regex = "^[a-zA-Z0-9_+.-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
return email != null && email.matches(regex);
}
// 注意:String.matches() 内部会自动编译 Pattern,适合一次性使用
2. 提取 HTML 标签中的内容
String html = "<div>Hello</div><p>World</p>";
String regex = "<(\\w+)>(.*?)</\\1>"; // \1 引用第一个分组 <div> 对应 </div>
Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); // DOTALL 让 . 匹配换行符
Matcher matcher = pattern.matcher(html);
while (matcher.find()) {
System.out.println("标签名: " + matcher.group(1)); // div, p
System.out.println("内容: " + matcher.group(2)); // Hello, World
}
3. 替换敏感词
String text = "这个产品非常棒,但是价格有点贵。";
String regex = "贵";
String replacement = "性价比低";
String result = text.replaceAll(regex, replacement);
System.out.println(result); // 这个产品非常棒,但是价格有点性价比低。
4. 分割字符串
String data = "apple,banana;orange|grape";
// 使用正则分割,支持多种分隔符
String[] fruits = data.split("[,;|]");
for (String f : fruits) {
System.out.println(f);
}
5. 性能优化(缓存 Pattern)
// ❌ 不推荐:每次调用都重新编译
public boolean check(String input) {
return input.matches("\\d{3}-\\d{4}");
}
// ✅ 推荐:静态缓存 Pattern
private static final Pattern PHONE_PATTERN = Pattern.compile("\\d{3}-\\d{4}");
public boolean check(String input) {
return PHONE_PATTERN.matcher(input).matches();
}
五、常用标志位 (Flags)
在 Pattern.compile(regex, flags) 中使用:
| 标志位 | 含义 |
|---|---|
Pattern.CASE_INSENSITIVE | 忽略大小写 |
Pattern.MULTILINE | 多行模式,^ 和 $ 匹配每行的开头结尾 |
Pattern.DOTALL | 单行模式,. 可以匹配换行符 \n |
Pattern.UNICODE_CASE | 配合 CASE_INSENSITIVE 支持 Unicode 大小写 |
Pattern.COMMENTS | 允许正则中包含注释和空白(需配合 # 注释) |
示例:
Pattern p = Pattern.compile("abc", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
六、常见陷阱与注意事项
1. 转义字符问题
Java 字符串中 \ 需要转义,正则中 \ 也需要转义。
- 匹配反斜杠:正则写
\\,Java 字符串写"\\\\"。 - 匹配数字:正则写
\d,Java 字符串写"\\d"。
2. 贪婪 vs 非贪婪
.*是贪婪的,会匹配尽可能多的字符。.*?是非贪婪的,匹配尽可能少的字符。- 示例:
<.*>匹配<a> <b>会匹配整个字符串;<.*?>会分别匹配<a>和<b>。
3. matches() vs find()
matches():要求整个字符串完全匹配正则。find():在字符串中查找是否存在匹配的子串。- 示例:
"abc123".matches("\\d+")返回false;"abc123".find("\\d+")返回true。
4. 性能问题
- 避免在循环中频繁编译正则。
- 避免使用复杂的嵌套分组或回溯过多的正则(可能导致 Catastrophic Backtracking,程序卡死)。
- 对于简单的分割或替换,优先使用
String类的方法(如split,replace),它们内部可能做了优化。
5. 特殊字符转义
在字符类 [] 中,大部分特殊字符不需要转义,但 ], \, ^, - 需要小心处理。
- 匹配
-:放在[]开头或结尾,或转义\\-。 - 匹配
]:必须转义\\]或放在[]开头。
七、总结
- 核心类:
Pattern(编译),Matcher(匹配)。 - 语法:掌握
\d,\w,*,+,?,(),|等核心元字符。 - 性能:重复使用的正则务必缓存
Pattern对象。 - 转义:注意 Java 字符串的双重转义。
- 工具:善用
String.matches(),split(),replaceAll()简化代码,但复杂逻辑用Pattern/Matcher。
正则表达式是处理文本的利器,熟练掌握能极大提升开发效率。建议配合在线正则测试工具(如 regex101.com)进行练习。
更多推荐
所有评论(0)