全面解析 Java 正则表达式:基础、语法与应用
Java 正则表达式(Regular Expressions,简称 regex 或 regexp)是一种强大的字符串处理工具,它允许我们通过模式匹配来搜索、替换、提取和验证字符串。无论是数据校验、日志分析还是文本处理,正则表达式都是 Java 开发者不可或缺的技能。本文将从基础概念、核心语法到实际应用,全面解析 Java 正则表达式。
一、正则表达式基础
1. 什么是正则表达式?
正则表达式是一个描述字符模式的对象。这些模式被用来匹配文本中的一系列字符串。在 Java 中,正则表达式通过 java.util.regex 包提供支持,主要包含三个核心类:
Pattern: 表示一个编译好的正则表达式。它是正则表达式的编译表示。Matcher: 是一个引擎,它通过解释Pattern来对输入字符串执行匹配操作。PatternSyntaxException: 非强制性异常,用于指示正则表达式模式中的语法错误。
2. 基本匹配
最简单的正则表达式就是普通字符。例如,正则表达式 a 会匹配字符串中所有出现的字母 “a”。
abc:匹配精确的字符串 “abc”。
二、正则表达式核心语法
Java 正则表达式的语法与 Perl 语言高度兼容,下面是一些常用的语法元素:
1. 字符类 (Character Classes)
字符类允许你匹配一组字符中的任意一个。
| 语法 | 描述 | 示例 | 匹配项 |
|---|---|---|---|
[abc] |
匹配 a、b 或 c 中的任意一个字符 |
[abc] “cat” |
c、a |
[^abc] |
匹配除了 a、b、c 之外的任意字符 |
[^abc] “dog” |
d、o、g |
[a-zA-Z] |
匹配所有大小写字母 | [a-z] “Hello” |
e、l、l、o |
[0-9] |
匹配所有数字(等同于 \d) |
[0-9] “123” |
1、2、3 |
[a-zA-Z0-9] |
匹配所有字母和数字 |
2. 预定义字符类 (Predefined Character Classes)
Java 提供了一些方便的预定义字符类来简化常用模式。
| 语法 | 描述 | 示例 | 匹配项 |
|---|---|---|---|
. |
匹配除换行符外的任何单个字符 | a.b “a*b” |
a*b |
\d |
匹配一个数字字符(等同于 [0-9]) |
\d “123” |
1、2、3 |
\D |
匹配一个非数字字符(等同于 [^0-9]) |
\D “abc” |
a、b、c |
\s |
匹配一个空白字符(空格、制表符、换页符等) | \s “a b” |
|
\S |
匹配一个非空白字符 | \S “a b” |
a、b |
\w |
匹配一个单词字符(字母、数字、下划线,等同于 [a-zA-Z_0-9]) |
\w “hello_1” |
h、e、l、l、o、_、1 |
\W |
匹配一个非单词字符 | \W “hi!” |
! |
3. 边界匹配器 (Boundary Matchers)
用于匹配字符串或单词的边界。
| 语法 | 描述 | 示例 | 匹配项 |
|---|---|---|---|
^ |
匹配行的开头 | ^cat “cat dog” |
cat |
$ |
匹配行的结尾 | dog$ “cat dog” |
dog |
\b |
匹配单词边界 | \bcat\b “cat” |
cat |
\B |
匹配非单词边界 | \Bcat\B “tomcat” |
cat |
4. 数量词 (Quantifiers)
数量词用于指定某个模式出现的次数。
| 语法 | 描述 | 示例 | 匹配项 |
|---|---|---|---|
? |
出现零次或一次 | colou?r |
color, colour |
* |
出现零次或多次 | a*b |
b, ab, aaab |
+ |
出现一次或多次 | a+b |
ab, aaab |
{n} |
出现恰好 n 次 | a{3}b |
aaab |
{n,} |
出现至少 n 次 | a{2,}b |
aab, aaab |
{n,m} |
出现 n 到 m 次 | a{1,3}b |
ab, aab, aaab |
贪婪、勉强和独占模式:
默认情况下,数量词是“贪婪的”(Greedy),它们会尽可能多地匹配字符。
– 贪婪 (Greedy):*, +, ?, {n,}, {n,m} 默认是贪婪的。
– .* 会匹配到行尾的最后一个字符。
– 勉强 (Reluctant / Lazy):在数量词后加 ?,使其尽可能少地匹配字符。
– *?, +?, ??, {n,}?, {n,m}?
– .*? 会匹配到它能匹配的最小字符串。
– 独占 (Possessive):在数量词后加 +,使其尽可能多地匹配字符,但不会回溯。这可以提高性能,但有时会因不回溯而导致匹配失败。
– *+, ++, ?+, {n,}+, {n,m}+
5. 逻辑运算符
| 语法 | 描述 | 示例 | 匹配项 |
|---|---|---|---|
XY |
X 后跟 Y | ab |
ab |
X|Y |
X 或 Y | cat|dog |
cat, dog |
(X) |
捕获组,将 X 作为一个整体处理 | (ab)+ |
ab, abab |
6. 反向引用 (Backreferences)
通过 (X) 捕获组匹配到的内容会被存储起来,可以通过 \n(n 为组号,从 1 开始)进行引用。
(\d)\1:匹配连续的两个相同数字,如 “11”, “22”。([a-z])\1\1:匹配连续的三个相同小写字母,如 “aaa”, “bbb”。
7. 转义字符 (Escaping)
特殊字符(如 ., *, +, ?, |, (, ), [, ], {, }, ^, $, \) 如果要匹配它们本身,需要使用反斜杠 \ 进行转义。在 Java 字符串中,反斜杠本身也是一个特殊字符,所以需要双重转义,即 \\。
\.:匹配字面量.。\\d:匹配数字字符。
三、Java 正则表达式应用
1. Pattern 和 Matcher 类
Java 中使用正则表达式的核心是 Pattern 和 Matcher 类。
“`java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexExample {
public static void main(String[] args) {
String text = “Hello, my email is [email protected] and another is [email protected].”;
String regex = “\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b”;
// 1. 编译正则表达式
Pattern pattern = Pattern.compile(regex);
// 2. 创建匹配器
Matcher matcher = pattern.matcher(text);
// 3. 执行匹配操作
System.out.println("查找所有邮箱地址:");
while (matcher.find()) {
// matcher.group() 返回与模式匹配的子序列
System.out.println(" 匹配到: " + matcher.group());
// matcher.start() 返回匹配到的子序列的起始索引
System.out.println(" 起始索引: " + matcher.start());
// matcher.end() 返回匹配到的子序列的结束索引加1
System.out.println(" 结束索引: " + matcher.end());
}
// 4. 替换操作
System.out.println("\n替换邮箱地址:");
String replacedText = matcher.replaceAll("[EMAIL_REDACTED]");
System.out.println(replacedText);
// 5. 字符串分割
System.out.println("\n根据数字分割字符串:");
String numbers = "one1two2three3four";
Pattern digitPattern = Pattern.compile("\\d");
String[] parts = digitPattern.split(numbers);
for (String part : parts) {
System.out.println(" 分割部分: " + part);
}
// 6. 验证(一次性匹配)
System.out.println("\n验证手机号码:");
String phoneNumber = "13812345678";
String invalidPhoneNumber = "12345";
// ^1[3-9]\d{9}$ 匹配以1开头,第二位是3-9,后面9位数字的11位手机号码
boolean isValid = Pattern.matches("^1[3-9]\\d{9}$", phoneNumber);
System.out.println("'" + phoneNumber + "' 是有效的手机号码吗? " + isValid);
boolean isInvalid = Pattern.matches("^1[3-9]\\d{9}$", invalidPhoneNumber);
System.out.println("'" + invalidPhoneNumber + "' 是有效的手机号码吗? " + isInvalid);
}
}
“`
2. String 类中的正则表达式方法
Java 的 String 类也提供了一些便利的方法,可以直接使用正则表达式。
boolean matches(String regex): 判断整个字符串是否匹配给定的正则表达式。String replaceAll(String regex, String replacement): 替换所有匹配正则表达式的子字符串。String replaceFirst(String regex, String replacement): 替换第一个匹配正则表达式的子字符串。String[] split(String regex): 根据正则表达式将字符串分割成字符串数组。
四、高级特性
1. 组 (Groups)
正则表达式中的括号 () 用于创建捕获组。每个捕获组都会有一个编号,从 1 开始。组 0 代表整个匹配的文本。
“`java
String text = “Name: John Doe, Age: 30”;
Pattern p = Pattern.compile(“Name: (\w+ \w+), Age: (\d+)”);
Matcher m = p.matcher(text);
if (m.find()) {
System.out.println(“完整匹配: ” + m.group(0)); // Name: John Doe, Age: 30
System.out.println(“姓名: ” + m.group(1)); // John Doe
System.out.println(“年龄: ” + m.group(2)); // 30
}
“`
2. 非捕获组 (Non-capturing Groups)
使用 (?:X) 可以创建非捕获组,它会匹配内容但不会存储,因此不会分配组号。这在只需要分组而不关心捕获内容时很有用,可以提高性能。
(?:\w+):匹配一个或多个单词字符,但不捕获。
3. 肯定/否定先行断言 (Lookahead Assertions)
X(?=Y)(肯定先行断言): 匹配X,当X后面跟着Y时。Y不会被包含在匹配结果中。Windows(?=\d): 匹配后面跟着数字的 “Windows”。
X(?!Y)(否定先行断言): 匹配X,当X后面没有跟着Y时。Windows(?!\d): 匹配后面没有跟着数字的 “Windows”。
4. 肯定/否定后行断言 (Lookbehind Assertions)
(?<=Y)X(肯定后行断言): 匹配X,当X前面是Y时。Y不会被包含在匹配结果中。(?<=\$)\d+: 匹配前面是$符号的数字。
(?<!Y)X(否定后行断言): 匹配X,当X前面不是Y时。(?<!\$)\d+: 匹配前面不是$符号的数字。
五、正则表达式的性能考虑
- 编译 Pattern:
Pattern.compile()是一个耗时操作。如果正则表达式被多次使用,应该将其编译一次并重用Pattern对象。 - 避免过度回溯: 复杂的正则表达式,特别是包含多个量词和交替操作的,可能会导致大量的回溯,从而降低性能甚至引发栈溢出。独占量词
*+,++可以避免回溯。 - 具体化模式: 尽可能使用更具体的模式来缩小匹配范围。例如,用
\d代替.来匹配数字。 - 使用非捕获组: 如果你不需要捕获组的内容,使用
(?:X)非捕获组可以稍微提高性能。
总结
Java 正则表达式是一个功能强大且灵活的工具,能够解决各种复杂的字符串处理问题。通过理解其基础概念、核心语法和应用方式,并注意性能优化,开发者可以更高效、更优雅地处理文本数据。掌握正则表达式是提升 Java 编程能力的关键一步。