核心概念
在 Java 中,与正则表达式相关的核心类有三个:
Pattern: 正则表达式的编译表示,它是一个不可变的类,用于定义一个模式,你可以把它看作是“规则”本身。- **
Matcher``: 对输入字符串进行解释和匹配操作的引擎,它是一个状态机器,用于执行Pattern` 定义的规则,你可以把它看作是“执行规则的裁判”。 String: Java 字符串类本身也提供了一些便捷方法(如matches(),replaceAll()等),它们内部会自动为你创建Pattern和Matcher对象。
基本使用流程
最标准、最灵活的使用流程分为三步:
编译正则表达式:Pattern.compile()
你需要将你的正则表达式字符串编译成一个 Pattern 对象,这样做的好处是,如果你需要对同一个正则表达式进行多次匹配,编译只需进行一次,可以提高效率。
import java.util.regex.Pattern; // 定义一个正则表达式,用于匹配一个或多个数字 String regex = "\\d+"; // 注意:在 Java 字符串中,反斜杠 \ 是一个转义字符,所以要匹配 \d 需要写成 \\d // 将正则表达式编译成一个 Pattern 对象 Pattern pattern = Pattern.compile(regex);
创建匹配器:pattern.matcher()
有了 Pattern 对象后,你可以用它来创建一个 Matcher 对象,并将你想要匹配的 String 传入。
String input = "我的电话是 123456,邮政编码是 100086"; // 创建一个 Matcher 对象,将输入字符串与模式进行关联 Matcher matcher = pattern.matcher(input);
执行匹配操作
Matcher 对象提供了丰富的方法来执行匹配操作:
| 方法 | 描述 | 示例 (输入: "abc123 is good") |
|---|---|---|
matches() |
整个字符串是否完全匹配模式。 | pattern.matches(".*\\d+.*") 返回 true |
lookingAt() |
字符串开头部分是否匹配模式。 | matcher.lookingAt() 返回 true (匹配 "abc123") |
find() |
在字符串中查找下一个匹配子串。 | matcher.find() 第一次返回 true (找到 "123") |
start() |
返回当前匹配子串的起始索引。 | matcher.start() 返回 3 |
end() |
返回当前匹配子串的结束索引 + 1。 | matcher.end() 返回 6 |
group() |
返回当前匹配的子串。 | matcher.group() 返回 "123" |
完整代码示例
下面是一个综合示例,演示了上述所有核心操作。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo {
public static void main(String[] args) {
// 1. 定义正则表达式并编译
// 匹配一个或多个数字
String regex = "\\d+";
Pattern pattern = Pattern.compile(regex);
// 2. 准备要匹配的字符串
String input = "订单号: 20251027001, 金额: 599.99, 用户ID: 8888";
// 3. 创建 Matcher
Matcher matcher = pattern.matcher(input);
System.out.println("原始字符串: \"" + input + "\"");
System.out.println("正则表达式: \"" + regex + "\"");
System.out.println("---------------------------------");
// 示例 1: find() - 查找所有匹配项
System.out.println("--- 使用 find() 查找所有数字 ---");
while (matcher.find()) {
// group(): 获取匹配到的字符串
// start(): 获取匹配的起始位置
// end(): 获取匹配的结束位置 (end - 1 是最后一个字符的索引)
System.out.println("找到匹配: '" + matcher.group() +
"', 位置: " + matcher.start() + " - " + (matcher.end() - 1));
}
System.out.println("---------------------------------");
// 重置 Matcher,以便重新匹配
matcher.reset();
// 示例 2: lookingAt() - 从开头匹配
System.out.println("--- 使用 lookingAt() 从开头匹配 ---");
if (matcher.lookingAt()) {
System.out.println("开头匹配成功: '" + matcher.group() + "'");
} else {
System.out.println("开头匹配失败");
}
System.out.println("---------------------------------");
// 重置 Matcher
matcher.reset();
// 示例 3: matches() - 整个字符串匹配
System.out.println("--- 使用 matches() 整个字符串匹配 ---");
// 修改输入字符串来测试 matches
String fullMatchInput = "20251027001";
Matcher fullMatcher = pattern.matcher(fullMatchInput);
if (fullMatcher.matches()) {
System.out.println("整个字符串匹配成功: '" + fullMatcher.group() + "'");
} else {
System.out.println("整个字符串匹配失败");
}
}
}
输出结果:
原始字符串: "订单号: 20251027001, 金额: 599.99, 用户ID: 8888"
正则表达式: "\d+"
---------------------------------
--- 使用 find() 查找所有数字 ---
找到匹配: '20251027001', 位置: 4 - 13
找到匹配: '599', 位置: 20 - 22
找到匹配: '8888', 位置: 31 - 34
---------------------------------
--- 使用 lookingAt() 从开头匹配 ---
开头匹配成功: '20251027001'
---------------------------------
--- 使用 matches() 整个字符串匹配 ---
整个字符串匹配成功: '20251027001'
String 类中的便捷方法
对于一些简单的匹配、替换或分割操作,你不需要手动创建 Pattern 和 Matcher。String 类本身提供了以下便捷方法:
boolean matches(String regex)
检查整个字符串是否匹配给定的正则表达式。
String str = "123abc";
// 检查 str 是否以数字开头,后跟任意字母
boolean isMatch = str.matches("\\d+.*"); // 返回 true
String replaceAll(String regex, String replacement)
替换所有匹配正则表达式的子串。
String str = "hello 123 world 456";
// 将所有数字替换为 "NUM"
String result = str.replaceAll("\\d+", "NUM"); // 结果: "hello NUM world NUM"
String replaceFirst(String regex, String replacement)
替换第一个匹配正则表达式的子串。
String str = "hello 123 world 456";
// 将第一个数字替换为 "NUM"
String result = str.replaceFirst("\\d+", "NUM"); // 结果: "hello NUM world 456"
String[] split(String regex)
根据正则表达式的匹配来拆分字符串。
String str = "apple,banana,orange,grape";
// 使用逗号或分号作为分隔符
String[] fruits = str.split("[,;]"); // 结果: ["apple", "banana", "orange", "grape"]
常用正则表达式元字符
掌握正则表达式,关键在于理解元字符的含义。
| 类别 | 元字符 | 描述 | 示例 |
|---|---|---|---|
| 字符类 | 匹配除换行符以外的任意单个字符。 | a.c 可以匹配 "abc", "aac", "a!c" |
|
[...] |
匹配方括号中的任意一个字符。 | [abc] 可以匹配 "a", "b", 或 "c" |
|
[^...] |
匹配不在方括号中的任意一个字符。 | [^abc] 可以匹配 "d", "1", "@" 等 |
|
[a-z] |
匹配 a 到 z 范围内的任意小写字母。 | [a-z] 可以匹配 "a" 到 "z" |
|
[A-Z] |
匹配 A 到 Z 范围内的任意大写字母。 | [A-Z] 可以匹配 "A" 到 "Z" |
|
[0-9] |
匹配 0 到 9 范围内的任意数字。 | 等同于 \d |
|
\d |
匹配数字,等同于 [0-9]。 |
\d{3} 可以匹配 "123", "456" |
|
\D |
匹配非数字字符。 | \D 可以匹配 "a", " ", "@" |
|
\w |
匹配单词字符 (字母, 数字, 下划线),等同于 [a-zA-Z0-9_]。 |
\w+ 可以匹配 "hello", "user_123" |
|
\W |
匹配非单词字符。 | \W 可以匹配 " ", "@", "#" |
|
\s |
匹配空白字符 (空格, 制表符 \t, 换行符 \n 等)。 |
\s+ 可以匹配 " ", "\t\n" |
|
\S |
匹配非空白字符。 | \S 可以匹配 "a", "1", "@" |
|
| 数量词 | 匹配前面的子表达式零次或多次。 | a* 可以匹配 "", "a", "aaa" |
|
| 匹配前面的子表达式一次或多次。 | a+ 可以匹配 "a", "aaa" (但不能是空串) |
||
| 匹配前面的子表达式零次或一次。 | a? 可以匹配 "", "a" |
||
{n} |
匹配前面的子表达式恰好 n 次。 | \d{3} 匹配恰好3个数字 |
|
{n,} |
匹配前面的子表达式至少 n 次。 | \d{2,} 匹配至少2个数字 |
|
{n,m} |
匹配前面的子表达式至少 n 次,最多 m 次。 | \d{2,4} 匹配2到4个数字 |
|
| 定位符 | ^ |
匹配字符串的开始位置。 | ^hello 匹配 "hello world" 的开头 |
| 匹配字符串的结束位置。 | world$ 匹配 "hello world" 的结尾 |
||
\b |
匹配单词边界 (单词的开头或结尾)。 | \bword\b 可以匹配 "word",但不能匹配 "sword" |
|
\B |
匹配非单词边界。 | \Bword\B 可以匹配 "swordword" 中的 "word" |
|
| 逻辑操作 | 或操作,匹配 两边的表达式。 | cat|dog 可以匹配 "cat" 或 "dog" |
|
| 分组,将括号内的表达式作为一个整体。 | (ab)+ 可以匹配 "ab", "abab", "ababab" |
||
| 非捕获分组,分组但不保存匹配结果,效率更高。 | (?:ab)+ 同上,但不创建捕获组 |
高级特性:捕获组
捕获组允许你从匹配的字符串中提取出特定的部分,用圆括号 来定义一个组。
String input = "姓名: 张三, 年龄: 30";
// 使用括号创建两个捕获组:一个用于姓名,一个用于年龄
String regex = "姓名: (.*), 年龄: (\\d+)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
// group(0) 是整个匹配的字符串
System.out.println("完整匹配: " + matcher.group(0)); // 输出: 姓名: 张三, 年龄: 30
// group(1) 是第一个捕获组
System.out.println("姓名: " + matcher.group(1)); // 输出: 张三
// group(2) 是第二个捕获组
System.out.println("年龄: " + matcher.group(2)); // 输出: 30
}
性能考虑与最佳实践
- 预编译模式 (
Pattern.compile): 如果你需要对同一个正则表达式进行多次匹配(在一个循环中处理多个字符串),务必先将其编译为Pattern对象,重复编译相同的正则表达式会严重影响性能。 - 避免贪婪模式: 默认情况下,数量词(如 , , )是“贪婪”的,它们会匹配尽可能多的字符,如果需要匹配尽可能少的字符,可以使用“懒惰”模式,即在数量词后加上 。
- 贪婪: 在
"<a> b </a>"中会匹配"<a> b </a>"。 - 懒惰: 在
"<a> b </a>"中会先匹配 ,然后是"<a>",然后是"<a> b ",最后是"<a> b </a>"。
- 贪婪: 在
- 使用具体的字符类: 尽量使用具体的字符类(如
\d代替[0-9],\w代替[a-zA-Z0-9_]),它们通常更高效且可读性更好。 - 注意回溯: 复杂的正则表达式,特别是包含嵌套量词(如
(a+)+)的,可能会导致“ catastrophic backtracking”(灾难性回溯),使程序在处理某些特定字符串时变得异常缓慢,在设计复杂模式时要警惕这种情况。
希望这份详细的指南能帮助你掌握 Java 中使用正则表达式处理 String 的技巧!
