正则表达式:用 Pattern 与 Matcher 匹配文本
本教程共 100 篇 · 第 64 篇 · 更新于 2026-08-05 · 约 6 分钟阅读
本节目标:学会用一串规则描述”什么样的字符串算合法”,并在 Java 里用 Pattern 和 Matcher 做匹配、提取、搜索和替换。
判断手机号、邮箱、邮编是否合法,如果全靠 if 一个个字符去比,代码又臭又长。正则表达式(regex)就是用”一串规则文字”来描述模式,再交给引擎去匹配,一行顶你几十行。
在 Java 里写正则要注意转义
正则本身就是字符串,而 \ 在 Java 字符串里也是转义符。所以正则里的 \d,写进 Java 要变成 "\\d":
String regex = "20\\d\\d"; // 正则含义:20 开头,后面两个数字
System.out.println("2019".matches(regex)); // true
System.out.println("2100".matches(regex)); // false
String.matches(regex) 是最简单的入口,判断整个字符串是否完全匹配该规则。
Warning正则里的
\在 Java 字符串里要写成\\。匹配一个反斜杠本身,正则写成\\,Java 字符串得写"\\\\"。这是新手最常栽的坑。
Java 15 引入了文本块(Text Block),写正则更清爽,不用双重转义了(但正则内部的 \ 还是要转):
String regex = """
\\d{3,4}-\\d{7,8} # 电话号码:3-4位区号-7-8位号码
""";
文本块用 """ 包裹,内部可以换行加注释,比一行字符串直观多了。
单个字符的匹配规则
| 写法 | 含义 | 能匹配 |
|---|---|---|
A | 指定字符 | A |
. | 任意字符 | a、&、0 |
\d | 数字 0~9 | 0~9 |
\w | 字母数字下划线 | a、9、_ |
\s | 空格或 Tab | 空格、\t |
\D \W \S | 上面的反义 | 非数字、非\w、非空白 |
"java9".matches("java\\d"); // true
"java#".matches("java\\w"); // false
量词:重复几次
光匹配一个字符不够,量词控制”重复多少遍”:
*:0 次或多次+:1 次或多次?:0 次或 1 次{n}:恰好 n 次{n,m}:n 到 m 次{n,}:至少 n 次
"A380".matches("A\\d+"); // true,\d+ 匹配 380
"010-12345678".matches("\\d{3,4}-\\d{7,8}"); // true
Note量词默认是”贪婪”的,会尽量多匹配。想变”非贪婪”,在量词后加
?,比如*?、+?、??。
复杂规则:开头结尾、范围、或
^表示字符串开头,$表示结尾,常用在整串校验。[...]表示字符范围:[1-9]、[a-fA-F];[^0-9]表示”非数字”。|表示”或”:java|php|go。
"^[1-9]\\d{5,6}$".matches("123456"); // 思路:6~7位、不以0开头
"java|php".matches("php"); // true
用括号 (...) 可以把公共部分提取出来:learn\s(java|php|go)。
边界匹配还有 \b(单词边界)和 \B(非单词边界),做”匹配整个单词而非子串”时很有用,比如 \bcat\b 不会匹配 category 里的 cat。
分组提取:Matcher.group
光判断还不够,常要从字符串里抠出子串。String.matches 做不到,得上 Pattern 和 Matcher:
import java.util.regex.*;
Pattern p = Pattern.compile("(\\d{3,4})-(\\d{7,8})");
Matcher m = p.matcher("010-12345678");
if (m.matches()) {
System.out.println(m.group(0)); // 010-12345678,整体
System.out.println(m.group(1)); // 010,第1个括号
System.out.println(m.group(2)); // 12345678,第2个括号
}
group(0) 是整个匹配,group(1)、group(2) 对应第几个 (...)。要先 matches() 成功才能取。
Tip同一个正则要反复用,先
Pattern.compile一次再反复matcher,比每次String.matches高效。编译一次,多次匹配。
命名分组(Java 7+)
Java 7 开始支持命名分组,比数字更直观:
Pattern p = Pattern.compile("(?<area>\\d{3,4})-(?<number>\\d{7,8})");
Matcher m = p.matcher("010-12345678");
if (m.matches()) {
System.out.println(m.group("area")); // 010
System.out.println(m.group("number")); // 12345678
}
(?<name>...) 给分组起名,用 m.group("name") 取值,比记数字更不容易出错。
贪婪 vs 非贪婪
正则默认”贪婪”,能多匹配就多匹配。看 (<br>)(\d+)(0*) 配 "123000":\d+ 会把后面 0 也吞掉,0* 拿到空。想在 \d+ 后加 ? 变非贪婪:
Pattern p = Pattern.compile("(\\d+?)(0*)");
Matcher m = p.matcher("123000");
m.matches();
System.out.println(m.group(1)); // 123
System.out.println(m.group(2)); // 000
? 紧跟量词后面,意思是”尽量少匹配”。
Warning贪婪/非贪婪是正则里最容易绕晕的概念。记住口诀:量词后加
?,“见好就收”;不加?,“能吞就吞”。
编译标志:忽略大小写等
Pattern.compile 第二个参数能传标志,改变匹配行为。最常用的 CASE_INSENSITIVE 忽略大小写,MULTILINE 让 ^/$ 匹配每行,DOTALL 让 . 也能匹配换行:
Pattern p = Pattern.compile("hello", Pattern.CASE_INSENSITIVE);
System.out.println(p.matcher("HELLO").matches()); // true
多个标志用 | 组合:Pattern.CASE_INSENSITIVE | Pattern.MULTILINE。
NoteJava 21 引入了
Pattern.COMMENTS的增强版,支持在正则里加注释和空白,大幅提升复杂规则的可读性。
搜索、替换与模板引擎
正则还常用在这几处:
// 1. 分割
"a, b ;; c".split("[,\\;\\s]+"); // ["a","b","c"]
// 2. 搜索
Pattern p = Pattern.compile("\\wo\\w");
Matcher m = p.matcher("the brown fox");
while (m.find()) {
System.out.println(m.group()); // 输出含 o 的三字母词
}
// 3. 替换
"a b c".replaceAll("\\s+", " "); // "a b c"
循环里 find() 会顺着文本往下找下一个匹配,start()/end() 还能拿到匹配的位置。想做复杂替换(比如模板引擎填充 ${name}),用 appendReplacement + appendTail:
Pattern p = Pattern.compile("\\$\\{(\\w+)\\}");
Matcher m = p.matcher("Hello, ${name}!");
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, "Bob");
}
m.appendTail(sb);
System.out.println(sb); // Hello, Bob!
安全细节
用户输入当正则时要小心:特殊字符会破坏规则。Pattern.quote 把一段文字当字面量处理,Matcher.quoteReplacement 在替换串里转义 $ 和 \:
Pattern.quote("a.b.c"); // 把点当成普通的点,不当"任意字符"
Warning正则写错会抛
PatternSyntaxException(运行时异常)。复杂规则建议先用在线正则测试工具验证,再贴进代码,省去反复试错的麻烦。
正则表达式性能注意
不要在循环里重复 Pattern.compile:
// 错的示范:每次循环都编译一次
for (String s : list) {
Pattern p = Pattern.compile("\\d+"); // 重复编译,性能差
p.matcher(s).matches();
}
// 正确做法:提到循环外面
Pattern p = Pattern.compile("\\d+"); // 编译一次
for (String s : list) {
p.matcher(s).matches();
}
Pattern.compile 有开销,编译一次反复用。在需要高并发的场景,可以把 Pattern 声明为 static final 常量,整个应用只编译一次。
Java 25 与老写法对照
正则相关 API 在 Java 25 变化不大,但新版本引入了一些便利写法:
| 写法 | 版本 | 状态 |
|---|---|---|
Pattern.compile(regex) | Java 1.4+ | ✅ 标准 |
Pattern.compile(regex, flags) | Java 1.4+ | ✅ 带标志 |
(?<name>...) 命名分组 | Java 7+ | ✅ 推荐 |
文本块写正则 """...""" | Java 15+ | ✅ 推荐 |
Matcher.results() 返回 Stream | Java 9+ | ✅ 函数式 |
Matcher.replaceAll(Function) | Java 9+ | ✅ 灵活替换 |
老代码里常见的 new Pattern() 是不存在的——Pattern 没有公开构造方法,只能用 Pattern.compile 获取实例。
速查表
| 语法 | 含义 |
|---|---|
. | 任意字符 |
\d \D | 数字 / 非数字 |
\w \W | 词字符 / 非词字符 |
\s \S | 空白 / 非空白 |
* + ? | 0或多 / 1或多 / 0或1 |
{n} {n,m} {n,} | 恰好n / n到m / 至少n |
^ $ | 开头 / 结尾 |
[...] [^...] | 字符范围 / 范围取反 |
| ` | ` |
(...) | 分组 |
(?<name>...) | 命名分组(Java 7+) |
*? +? ?? | 非贪婪量词 |
\b \B | 单词边界 / 非单词边界 |
| 方法 | 用途 |
|---|---|
String.matches(regex) | 整串匹配 |
Pattern.compile(regex) | 编译正则 |
Matcher.matches() | 整串匹配 |
Matcher.find() | 搜索下一个匹配 |
Matcher.group() | 取匹配内容 |
Matcher.start() end() | 取匹配位置 |
String.split(regex) | 分割 |
String.replaceAll(regex, repl) | 替换全部 |
Pattern.quote(s) | 转义成字面量 |
常见疑问
Q:Java 正则和其他语言的正则一样吗?
基本语法大同小异,但实现细节有差别。Java 用 Pattern/Matcher,转义用 \\,没有 /regex/ 的写法。正则不是标准,但核心语法(量词、分组、字符类)各语言通用。
Q:matches() 和 find() 有什么区别?
matches() 要求整个字符串完全匹配规则。find() 只要求在字符串中找到一处匹配就行。校验手机号用 matches,搜索文本里的关键词用 find。
Q:为什么 replaceAll 里 $ 和 \ 要转义?
替换串里的 $1 表示引用第一组,\ 是转义符。普通字符串想表示字面的 $ 或 \,要用 Matcher.quoteReplacement 包一层,避免被当成特殊符号解析。
Q:正则表达式怎么调试?
推荐两个思路:一是在线工具(如 regex101.com)先验证规则,选好 Java 方言再贴进代码;二是 Java 里用 Pattern.matcher(s).results().count() 快速验证匹配数量。
Q:如何匹配中文字符?
用 \p{Script=Han} 匹配汉字(Java 7+),或者用 [\u4e00-\u9fa5] 匹配基本汉字。\w 不匹配中文,这点和 Perl/Python 不一样,别踩坑。