1. 正则表达式简介
1.1 什么是正则表达式
正则表达式(Regular Expression,简称 Regex)是一种用于 描述字符串模式的工具。不是简单的字符串匹配,而是用 规则和模式 来定义哪些字符串是“符合要求”的。
用途举例:
校验文本格式:手机号、邮箱、身份证、邮编等
搜索文本:文章关键词查找、日志匹配
替换文本:敏感信息、格式统一
提取信息:日志分析、网页抓取、数据清洗
1.2 为什么使用正则表达式
举个例子:判断一个字符串是否为手机号
传统方法:
String s = "13800138000";
boolean valid = s.length() == 11;
for (int i = 0; i < s.length(); i++) {
if (!Character.isDigit(s.charAt(i))) {
valid = false;
break;
}
}
问题:
代码冗长
可读性差
规则一变,需要改逻辑
正则方法:
"13800138000".matches("\\d{11}")
一句话搞定:
\d{11}:表示 连续11个数字matches():表示整个字符串必须完全匹配
正则把逻辑变成了规则描述,不需要写循环和判断。
2. 正则表达式的工作原理
正则表达式本质上是一种 模式匹配引擎,它并不是直接去判断字符串,而是通过一套复杂的逻辑对输入字符串进行扫描、尝试匹配,并返回结果。理解其原理可以写出更高效、更正确的正则,也能理解为什么有些正则匹配慢或者匹配失败。
2.1 匹配流程
正则引擎匹配字符串的流程可以拆解为三个主要步骤:模式解析、扫描字符串、返回结果
模式解析
在 Java 中写下一个正则表达式,例如 \d{3}-\d{2}-\d{4},Java 正则引擎首先会把这个字符串 编译成一个内部状态机。这个状态机有两种常见实现方式:
NFA(非确定有限自动机)
NFA 是一种图结构,每个节点表示一个匹配状态
当遇到多种匹配可能时,会保留分支,并在必要时回溯
Java 的正则引擎大部分使用 NFA 实现,因此支持复杂的量词和分组
DFA(确定有限自动机)
DFA 不回溯,每个输入字符只会走一条确定路径
匹配速度非常快,但对某些复杂模式的支持有限
Java 默认不使用 DFA,但理解 DFA 有助于理解正则效率问题
这个阶段的作用就是把“正则规则”转化成 计算机能够理解的逻辑结构,方便后续匹配操作。
扫描字符串
正则引擎从目标字符串的 第一个字符开始,逐字符尝试匹配模式。匹配过程中可能出现两种情况:
匹配成功
当前字符满足模式要求
引擎继续尝试匹配下一个字符
如果模式中包含量词或分组,可能会尝试多种匹配路径
匹配失败
当前字符不满足模式要求
引擎会尝试 回溯:回到之前的分支点,尝试其他匹配路径
如果没有可回溯的路径,匹配失败
引擎可能会从字符串的 下一个起始位置 重新尝试匹配
举例说明:
假设模式是 \d{2},字符串是 "a12":
引擎从第 0 个字符
"a"开始匹配"a"不匹配\d,引擎失败移动到第 1 个字符
"1"作为新的起始位置"1"匹配第一个\d"2"匹配第二个\d匹配成功,返回结果
"12"
这说明正则匹配不仅仅是从左到右一次完成,而是可能回退、重新尝试,直到找到符合模式的子串或扫描完整个字符串。
返回结果
匹配完成后,正则引擎会返回匹配结果,情况有两种:
匹配成功
引擎返回匹配到的字符串
如果使用分组,可以返回不同分组捕获的内容
可以用于提取、替换、验证
匹配失败
返回 null 或空
对于
find()方法,会继续尝试下一个起始位置对于
matches()方法,如果整个字符串没有完全匹配,则直接返回 false
2.2 完全匹配与部分匹配
在 Java 正则中,理解 完全匹配 与 部分匹配 的区别非常重要,因为它直接决定使用的方法是 matches() 还是 find()。
完全匹配(matches)
matches()方法要求 整个字符串必须从头到尾都符合正则模式如果字符串中有多余字符或不符合规则的字符,匹配失败
String str = "abc123";
boolean result = str.matches("\\d+"); // 检查字符串是否完全由数字组成
System.out.println(result); // 输出 false
解析:
正则
\d+表示 一个或多个数字字符串
"abc123"前面有非数字字符"abc"因此整个字符串不完全匹配,返回 false
部分匹配(find)
find()方法则用来查找字符串中 任意一段符合正则的子串不要求整个字符串匹配,只要存在一段满足模式即可
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("abc123");
if (matcher.find()) {
System.out.println(matcher.group()); // 输出 123
}
解析:
正则仍然是
\d+字符串
"abc123"中存在"123"满足模式find()找到匹配的子串"123"并返回即使前面
"abc"不匹配,也不会影响结果
3. Java 中正则表达式核心类
Java 使用两个类处理正则表达式:
Pattern
表示编译后的正则表达式
Pattern.compile("正则表达式")
Matcher
用于执行匹配操作
pattern.matcher("目标字符串")
Pattern pattern = Pattern.compile("\\d{11}");
Matcher matcher = pattern.matcher("13800138000");
if (matcher.matches()) {
System.out.println("是手机号");
}
4. 正则基础语法与元字符
正则表达式通过 元字符(Metacharacter) 来定义匹配规则。
4.1 常用单字符匹配
4.2 字符集合与范围
[abc]→ 匹配 a 或 b 或 c[a-z]→ 匹配小写字母[A-Z0-9]→ 匹配大写字母或数字[^abc]→ 匹配不包含 a/b/c 的任意字符
"a".matches("[abc]") // true
"x".matches("[^abc]") // true
5. 常用量词与分组
5.1 量词
5.2 分组与捕获
( )→ 分组|→ 或捕获分组可用
Matcher.group()提取
String s = "2025-12-31";
Pattern p = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher m = p.matcher(s);
if (m.find()) {
System.out.println(m.group(1)); // 年
System.out.println(m.group(2)); // 月
System.out.println(m.group(3)); // 日
}
6. Java 正则示例
6.1 匹配邮箱
String text = "我的邮箱是 example@test.com";
String regex = "[a-zA-Z0-9_.]+@[a-zA-Z0-9]+\\.[a-zA-Z]{2,}";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
System.out.println("邮箱:" + matcher.group());
}
6.2 查找所有数字
String text2 = "手机号:13800138000 邮编:100000";
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher(text2);
while (m.find()) {
System.out.println("数字:" + m.group());
}
6.3 替换敏感信息
String text3 = "手机号:13800138000";
String replaced = text3.replaceAll("\\d{11}", "***********");
System.out.println(replaced);
7. 贪婪匹配与懒惰匹配
贪婪匹配是正则表达式的默认行为,它会尽可能多地匹配字符,只要整个匹配仍然成功。比如正则表达式
a.*b用于匹配字符串 "a123b456b"。在这个例子中,.*会尽可能多地匹配字符,直到最后一个符合b的位置。匹配结果是 "a123b456b",因为正则引擎尽量把.*匹配的范围扩大到最远。"<div>1</div><div>2</div>".replaceAll("<.*>", "X"); // 输出 X懒惰匹配是通过在量词后加问号
?实现的,它表示尽可能少地匹配字符。使用懒惰匹配时,正则引擎会从最少字符开始尝试匹配,然后根据整体模式调整匹配长度。以上面的例子为例,如果使用正则表达式a.*?b来匹配 "a123b456b",.*?会尽量少匹配字符,第一个匹配到的b就会停止匹配,因此匹配结果是 "a123b"。如果字符串中有多个符合条件的子串,懒惰匹配会优先匹配最短的部分。"<div>1</div><div>2</div>".replaceAll("<.*?>", "X"); // 输出 XX