核心概念
在 Java 中,正则表达式主要通过 java.util.regex 包中的两个类来实现:
Pattern: 一个正则表达式编译后的表示形式,它是一个不可变的类,你首先需要将你的正则表达式字符串编译成一个Pattern对象。Matcher: 一个引擎,它对输入字符串进行解释和匹配操作,它是由Pattern对象创建的,并提供了各种方法来执行匹配、查找和替换操作。
简单流程:
正则表达式字符串 -> Pattern.compile() -> Pattern 对象 -> pattern.matcher() -> Matcher 对象 -> matcher.find() / matcher.matches() / ... -> boolean 或 匹配到的结果
常用匹配方法
Matcher 类提供了三种主要的匹配方法,它们的行为各不相同,非常重要:
| 方法 | 描述 | 示例 (正则: "\\d+", 字符串: "abc123def456") |
|---|---|---|
matches() |
整个字符串必须完全匹配正则表达式模式。 | 返回 false,因为字符串 "abc123def456" 不完全是一个或多个数字。 |
lookingAt() |
从字符串的开头开始匹配,但不需要匹配整个字符串。 | 返回 true,因为开头部分 "123" 匹配 \\d+。 |
find() |
在字符串中查找与模式匹配的子序列,可以多次调用。 | 第一次调用返回 true,找到 "123",第二次调用返回 true,找到 "456",第三次调用返回 false。 |
完整代码示例
下面是一个完整的示例,演示了这三种核心方法以及其他常用功能。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexExample {
public static void main(String[] args) {
// 1. 定义要匹配的字符串
String text = "Hello, my phone number is 123-456-7890, and my email is test.user@example.com.";
// 2. 定义正则表达式
// \d 表示任意数字 [0-9]
// {3} 表示前面的元素(这里是\d)恰好出现3次
// - 是一个普通字符,需要转义为 \-
// \. 匹配点号,因为 . 在正则中有特殊含义(匹配任意字符),所以需要转义
// + 表示前面的元素(这里是 [a-zA-Z0-9])出现一次或多次
// * 表示前面的元素(这里是 \.)出现零次或多次
String phoneRegex = "\\d{3}-\\d{3}-\\d{4}";
String emailRegex = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}";
// 3. 编译正则表达式,创建 Pattern 对象
// 好的做法是将编译好的 Pattern 缓存起来(例如使用 static final),因为编译是开销较大的操作
Pattern phonePattern = Pattern.compile(phoneRegex);
Pattern emailPattern = Pattern.compile(emailRegex);
// --- 核心匹配方法演示 ---
// a) matches() - 整个字符串匹配
System.out.println("--- 使用 matches() ---");
boolean isPhoneFullMatch = phonePattern.matcher("123-456-7890").matches();
System.out.println("'123-456-7890' 是否完全匹配电话号码格式? " + isPhoneFullMatch); // true
boolean isTextPhoneMatch = phonePattern.matcher(text).matches();
System.out.println("整个 text 是否完全匹配电话号码格式? " + isTextPhoneMatch); // false
System.out.println("------------------------------------\n");
// b) lookingAt() - 从开头匹配
System.out.println("--- 使用 lookingAt() ---");
boolean isTextStartsWithPhone = phonePattern.matcher(text).lookingAt();
System.out.println("text 是否以电话号码格式开头? " + isTextStartsWithPhone); // false
System.out.println("------------------------------------\n");
// c) find() - 查找匹配的子串
System.out.println("--- 使用 find() ---");
// 创建 Matcher 对象
Matcher phoneMatcher = phonePattern.matcher(text);
System.out.println("在 text 中查找所有电话号码:");
while (phoneMatcher.find()) {
// group() 方法返回最近一次匹配到的子串
System.out.println("找到电话号码: " + phoneMatcher.group());
}
System.out.println("------------------------------------\n");
// 查找所有邮箱
Matcher emailMatcher = emailPattern.matcher(text);
System.out.println("在 text 中查找所有邮箱:");
while (emailMatcher.find()) {
System.out.println("找到邮箱: " + emailMatcher.group());
}
System.out.println("------------------------------------\n");
// --- 其他常用功能 ---
// d) 获取匹配的详细信息
System.out.println("--- 获取匹配的详细信息 ---");
String sampleText = "Order 123 is confirmed.";
Matcher orderMatcher = Pattern.compile("(Order) (\\d+)").matcher(sampleText);
if (orderMatcher.find()) {
System.out.println("完整匹配: " + orderMatcher.group(0)); // group(0) 是整个匹配
System.out.println("第一个捕获组 (Order): " + orderMatcher.group(1)); // group(1) 是第一个括号里的内容
System.out.println("第二个捕获组 (123): " + orderMatcher.group(2)); // group(2) 是第二个括号里的内容
// start() 和 end() 返回匹配的起始和结束索引(end 是 exclusive 的)
System.out.println("匹配的起始位置: " + orderMatcher.start());
System.out.println("匹配的结束位置: " + orderMatcher.end());
}
System.out.println("------------------------------------\n");
// e) 替换匹配的文本
System.out.println("--- 使用 replaceAll() ---");
String anonymizedText = text.replaceAll("\\d{3}-\\d{3}-\\d{4}", "[PHONE-NUMBER]");
System.out.println("替换后的文本: " + anonymizedText);
String textWithUnderscores = text.replaceAll("\\s", "_"); // \s 匹配任何空白字符
System.out.println("空格替换为下划线: " + textWithUnderscores);
System.out.println("------------------------------------\n");
// f) 分割字符串
System.out.println("--- 使用 split() ---");
String csvData = "apple,banana,cherry,date";
String[] fruits = Pattern.compile(",").split(csvData);
for (String fruit : fruits) {
System.out.println("水果: " + fruit);
}
System.out.println("------------------------------------\n");
}
}
常用正则表达式元字符
为了让你能写出自己的正则表达式,这里是一些最常用的元字符:
| 元字符 | 描述 | 示例 |
|---|---|---|
| 匹配除换行符以外的任意单个字符 | c.t 可以匹配 "cat", "cot", "c8t" |
|
| 匹配前面的元素零次或多次 | go*gle 可以匹配 "ggle", "gogle", "google" |
|
| 匹配前面的元素一次或多次 | go+gle 可以匹配 "gogle", "google" (但不能匹配 "ggle") |
|
| 匹配前面的元素零次或一次 | colou?r 可以匹配 "color" 和 "colour" |
|
{n} |
匹配前面的元素恰好 n 次 | \d{3} 匹配3个数字 |
{n,} |
匹配前面的元素至少 n 次 | \d{2,} 匹配2个或更多数字 |
{n,m} |
匹配前面的元素至少 n 次,但不超过 m 次 | \d{2,4} 匹配2到4个数字 |
[] |
字符类,匹配方括号内的任意一个字符 | [abc] 匹配 "a", "b", 或 "c" |
[^] |
否定字符类,匹配不在方括号内的任意字符 | [^0-9] 匹配任意非数字字符 |
[a-z] |
匹配指定范围内的任意字符 | [a-z] 匹配任意小写字母 |
\d |
匹配数字,等同于 [0-9] |
\d 匹配 "1", "2", "9" |
\D |
匹配非数字字符,等同于 [^0-9] |
\D 匹配 "a", " ", "#" |
\w |
匹配单词字符,等同于 [a-zA-Z0-9_] |
\w 匹配 "a", "Z", "5", "_" |
\W |
匹配非单词字符,等同于 [^a-zA-Z0-9_] |
\W 匹配 " ", "@", "#" |
\s |
匹配空白字符(空格, tab, 换行等) | \s 匹配 " ", "\t", "\n" |
\S |
匹配非空白字符 | \S 匹配 "a", "1", "@" |
^ |
匹配字符串的开头 | ^Hello 匹配 "Hello world" 的开头 |
| 匹配字符串的 | world$ 匹配 "Hello world" 的结尾 |
|
| 捕获组,将括号内的表达式作为一个整体 | (ab)+ 匹配 "ab", "abab" |
|
\ |
转义字符,用于匹配元字符本身 | \. 匹配一个真实的点号 |
\| |
或,匹配两个或多个表达式之一 | cat\|dog 匹配 "cat" 或 "dog" |
性能提示
-
预编译
Pattern:Pattern.compile()是一个相对耗时的操作,如果你的同一个正则表达式要在循环或频繁调用的代码中使用,请务必将其编译为static final的Pattern对象,并重复使用它。// 推荐 private static final Pattern PHONE_PATTERN = Pattern.compile("\\d{3}-\\d{3}-\\d{4}"); // 不推荐 (每次调用都重新编译) public boolean isPhone(String s) { return Pattern.compile("\\d{3}-\\d{3}-\\d{4}").matcher(s).matches(); } -
避免贪婪匹配: �情况下,量词(如 , , )是贪婪的,它们会匹配尽可能多的字符,如果你只需要匹配到第一个结束标签,可以使用懒惰(或非贪婪)模式,即在量词后加上 。
- 贪婪:
<div>.*</div>会匹配从第一个<div>到最后一个</div>之间的所有内容。 - 懒惰:
<div>.*?</div>会匹配从第一个<div>到第一个</div>之间的内容。
- 贪婪:
希望这份详细的指南能帮助你掌握 Java 正则表达式的使用!
