Skip to content

Java 正则表达式

正则表达式(Regular expressions,简称 Regex)是定义搜索模式的字符序列。它们是进行模式匹配、搜索和文本操作的强大工具。

Java 提供了 java.util.regex 包来处理正则表达式。其语法与 Perl 大致兼容。

  • Pattern 类: 表示一个编译后的正则表达式。编译模式通常是第一步,并且可能计算量较大,因此如果模式被重复使用,编译一次会更高效。Pattern 对象是不可变(immutable)且线程安全(thread-safe)的。你可以使用静态方法 compile() 创建一个 Pattern 对象:Pattern p = Pattern.compile(regexString);
  • Matcher 类: 是一个引擎,用于使用 Pattern 对输入字符序列执行匹配操作。通过在 Pattern 对象上调用 matcher(input) 方法来获取 Matcher 对象:Matcher m = p.matcher(inputString);。Matcher 对象是有状态的,并且不是线程安全的。
  • PatternSyntaxException: 一个未受检异常(unchecked exception),在编译时指示正则表达式模式存在语法错误。
  1. 将你的正则表达式定义为一个字符串。
  2. 使用 Pattern.compile() 将 regex 字符串编译成一个 Pattern 对象。
  3. 使用 pattern.matcher(inputText) 从 Pattern 和你的输入文本创建一个 Matcher 对象。
  4. 使用 Matcher 的方法(例如 find()、matches()、lookingAt()、group())执行匹配操作并检索结果。

正则表达式中的括号 () 会创建捕获组。它们允许你:

  • 将多个字符视为一个单元(例如,用于应用量词如 *、+、?)。
  • 提取匹配文本的特定部分。

组从左到右根据其左括号进行编号,从 1 开始。组 0 总是表示整个匹配项。

import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexGroupsDemo {
public static void main(String[] args) {
String text = "User: alice ID: 101, User: bob ID: 205";
// 正则表达式用于查找 'User: (name)' 和 'ID: (number)'
// (\w+) 捕获用户名(单词字符)
// (\d+) 捕获 ID(数字)
String regex = "User: (\\w+) ID: (\\d+)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
// 使用 find() 在文本中查找匹配项
while (matcher.find()) {
System.out.println("完整匹配(组 0):" + matcher.group(0));
System.out.println("用户名(组 1): " + matcher.group(1));
System.out.println("用户 ID(组 2): " + matcher.group(2));
System.out.println("起始索引:" + matcher.start() + ", 结束索引:" + matcher.end());
System.out.println("---");
}
}
}

预期输出:

Full match (Group 0): User: alice ID: 101
Username (Group 1): alice
User ID (Group 2): 101
Start index: 0, End index: 19
---
Full match (Group 0): User: bob ID: 205
Username (Group 1): bob
User ID (Group 2): 205
Start index: 21, End index: 38
---

你可以使用 matcher.groupCount() 查找捕获组的数量(不包括组 0)。

常见元字符(metacharacters)和构造:

语法描述
.任何单个字符(换行符除外,除非设置了 DOTALL 标志)
^行的开头(或字符串开头,若设置了 MULTILINE 标志)
$行的结尾(或字符串结尾,若设置了 MULTILINE 标志)
[...]字符类:匹配括号内的任何单个字符(例如,[abc])
[^...]否定字符类:匹配不在括号内的任何单个字符(例如,[^0-9])
A|B交替:匹配 A 或 B
(...)捕获组
(?:...)非捕获组(分组字符但不捕获)
\d数字:[0-9]
\D非数字:[^0-9]
\s空白字符:[ \t\n\x0B\f\r]
\S非空白字符
\w单词字符:[a-zA-Z_0-9]
\W非单词字符
\b单词边界
\B非单词边界
*量词:匹配前一个元素的零次或多次出现
+量词:匹配前一个元素的一次或多次出现
?量词:匹配前一个元素的零次或一次出现
{n}量词:精确匹配 n 次出现
{n,}量词:至少匹配 n 次出现
{n,m}量词:至少匹配 n 次但不超过 m 次出现
*?, +?, ??, {n,}? 等惰性/非贪婪量词:匹配最短可能的字符串

注意:反斜杠 (\) 在 Java 字符串字面量(String literals)中需要转义(例如,\d 变成 "\\d")。

方法描述
boolean find()尝试查找输入序列中与模式匹配的下一个子序列(subsequence)。如果找到则返回 true,否则返回 false。常用于 while 循环。
boolean find(int start)重置匹配器并从指定索引开始搜索。
boolean matches()尝试将整个输入序列与模式进行匹配。仅当整个输入序列与模式匹配时才返回 true。
boolean lookingAt()尝试从输入序列的开头开始匹配模式。不要求整个输入序列都匹配。
方法描述
String group() or String group(0)返回上一次匹配所对应的输入子序列(完整匹配)。
String group(int group)返回在上一次匹配期间由给定组号捕获的子序列。
String group(String name)返回由给定命名组 (?<name>...) 捕获的子序列。
int groupCount()返回模式中的捕获组数量(不包括组 0)。
int start() or int start(0)返回上一次匹配的起始索引。
int start(int group)返回由给定组捕获的子序列的起始索引。
int end() or int end(0)返回上一次匹配的最后一个字符之后的偏移量。
int end(int group)返回由给定组捕获的最后一个字符之后的偏移量。
方法描述
String replaceAll(String replacement)将输入中所有与模式匹配的子序列替换为替换字符串。
String replaceFirst(String replacement)只替换第一个与模式匹配的子序列。
Matcher appendReplacement(StringBuffer sb, String replacement) or Matcher appendReplacement(StringBuilder sb, String replacement)将当前匹配项之前的部分文本以及替换文本追加到给定的缓冲区。允许进行复杂的替换,包括在替换字符串中使用捕获组($1、$2 等)。
StringBuffer appendTail(StringBuffer sb) or StringBuilder appendTail(StringBuilder sb)将输入序列的剩余部分(在最后一个匹配项之后的部分)追加到缓冲区。通常在 appendReplacement 循环之后使用。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexReplaceDemo {
private static final String REGEX = "\b(cat|dog|fish)\b"; // 匹配完整的单词:cat, dog, 或 fish
private static final String INPUT = "The quick brown fox jumps over the lazy dog. The cat naps.";
private static final String REPLACE_ALL = "animal";
private static final String REPLACE_FIRST = "pet";
public static void main(String[] args) {
Pattern p = Pattern.compile(REGEX);
Matcher m = p.matcher(INPUT);
// 替换所有匹配项
String resultAll = m.replaceAll(REPLACE_ALL);
System.out.println("Replace All: " + resultAll);
// 只替换第一个匹配项(需要新的匹配器或重置)
m.reset(); // 将匹配器重置到输入的开头
String resultFirst = m.replaceFirst(REPLACE_FIRST);
System.out.println("Replace First: " + resultFirst);
}
}

预期输出:

Replace All: The quick brown fox jumps over the lazy animal. The animal naps.
Replace First: The quick brown fox jumps over the lazy pet. The cat naps.

示例:appendReplacement 和 appendTail(使用 StringBuilder)

Section titled “示例:appendReplacement 和 appendTail(使用 StringBuilder)”
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexAppendDemo {
private static final String REGEX = "(John|Jane)";
private static final String INPUT = "John Doe met Jane Smith. John works hard.";
private static final String REPLACE_FORMAT = "**$1**"; // $1 指代第一个捕获组
public static void main(String[] args) {
Pattern p = Pattern.compile(REGEX);
Matcher m = p.matcher(INPUT);
StringBuilder sb = new StringBuilder();
while (m.find()) {
// 追加匹配项之前的文本和替换文本
m.appendReplacement(sb, REPLACE_FORMAT);
}
// 追加最后一个匹配项之后的任何剩余文本
m.appendTail(sb);
System.out.println(sb.toString());
}
}

预期输出:

**John** Doe met **Jane** Smith. **John** works hard.
  • 编译一次,重复使用: 编译 Pattern 对象的开销较大。如果你多次使用相同的正则表达式,请编译一次并重复使用 Pattern 对象。
  • 避免在热点循环(Hot Loops)中使用复杂正则表达式: 非常复杂的模式或编写低效的正则表达式可能会显著影响性能。
  • 考虑替代方案: 对于简单的子字符串搜索或分割,标准的 String 方法(contains()、indexOf()、split())可能比正则表达式更快。