✅ 一、字符类匹配(Character Classes)
| 表达式 | 含义 | 示例匹配 |
|---|---|---|
. | 任意单个字符(不包括换行) | a、1、$ |
[abc] | 匹配 a、b 或 c 中的任意一个字符 | a、b、c |
[^abc] | 匹配非 a、b、c 的字符 | d、1 |
[a-z] | 匹配 a 到 z 的任意小写字母 | g、m |
[A-Z] | 匹配 A 到 Z 的大写字母 | Q、M |
[0-9] | 匹配任意一位数字 | 3、7 |
[a-zA-Z0-9_] | 匹配字母、数字或下划线 | x、Z、0 |
✅ 二、预定义字符类
| 表达式 | 含义 | 示例 |
|---|---|---|
\d | 数字,等同于 [0-9] | 0、5 |
\D | 非数字,等同于 [^0-9] | a、$ |
\w | 字母数字下划线,等同于 [a-zA-Z0-9_] | a、8、_ |
\W | 非字母数字下划线 | #、空格 |
\s | 空白字符(空格、制表、换行) | ' '、\n |
\S | 非空白字符 | x、9 |
⚠ Java 字符串中需双重转义:如写 \\d 才是正则的 \d
✅ 三、数量词(重复)
| 表达式 | 含义 | 匹配示例 |
|---|---|---|
? | 重复0次或1次 | a? → ""、a |
+ | 重复1次或多次 | a+ → a、aa |
* | 重复0次或多次 | a* → ""、a、aaa |
{n} | 重复n次 | a{3} → aaa |
{n,} | 重复至少n次 | a{2,} → aa、aaaaa |
{n,m} | 重复n到m次 | a{2,4} → aa、aaa |
✅ 四、边界匹配符
| 表达式 | 含义 | 示例 |
|---|---|---|
^ | 匹配输入的开始位置 | ^abc → 匹配以 abc 开头 |
$ | 匹配输入的结束位置 | abc$ → 匹配以 abc 结尾 |
\b | 单词边界 | \bword\b 匹配完整单词 |
\B | 非单词边界 | \Bend → 不在边界处匹配 |
✅ 五、分组与捕获
分组的主要作用:
1.将一段模式作为一个整体进行操作(比如重复、量词作用于整个分组)。
例子:匹配重复出现的 “abc”
(abc)+
2.组织正则表达式结构,使表达式更清晰。
例子:匹配电话号码(带区号和连字符)
(\d{3})-(\d{3})-(\d{4})
3. 分组配合选择符 |
例子:匹配重复出现的 “abc”
(abc)+
- 说明:括号把
"abc"作为一个整体 - 量词
+作用于整个分组,表示"abc"至少出现一次,可以连续出现多次 - 匹配:
abc、abcabc、abcabcabc等
| 表达式 | 含义 | 示例 |
|---|---|---|
(abc) | 匹配并捕获 abc | (ab)+ → abab |
(?:abc) | 匹配但不捕获组 | 用于性能优化 |
| `(a | b)` | 匹配 a 或 b |
\1, \2... | 引用前面第几个捕获组 | 重复匹配 (\w+)\s\1 解释:匹配一个单词(用 (\w+) 捕获),后面跟一个空格,再跟着和第一个捕获组完全相同的单词字符串 "hello hello" 会匹配,因为第二个单词和第一个单词相同字符串 "hello world" 不匹配 |
✅ 六、零宽断言(高级)
| 表达式 | 含义 | 示例 |
|---|---|---|
(?=abc) | 正向先行断言,后面是 abc | a(?=b)→ 匹配 a,后面必须是 b |
(?!abc) | 负向先行断言,后面不是 abc | a(?!b)→ 匹配 a,后面不能是 b |
(?<=abc) | 正向后行断言,前面是 abc | (?<=a)b→ 匹配 b,前面必须是 a |
(?<!abc) | 负向后行断言,前面不是 abc | (?<!a)b→ 匹配 b,前面不能是 a |
注意:Java 从 9 开始才支持 (?<=...) 等 后行断言
✅ 七、贪婪模式(Greedy)
定义
- 贪婪模式是正则表达式默认的匹配方式。
- 它会尝试匹配尽可能多的字符,直到整个表达式无法再匹配更长字符串为止。
- 它满足匹配条件后不会停止,而是尽量往后多匹配。
量词符号(贪婪)
| 量词 | 含义 |
|---|---|
* | 0 次或多次 |
+ | 1 次或多次 |
? | 0 次或 1 次 |
{n,} | 至少 n 次 |
例子
字符串:
css
复制编辑
"<div>content</div><div>more</div>"
正则:
regex
复制编辑
<.*>
.*是贪婪匹配,尽可能多匹配。- 匹配结果是:
css
复制编辑
<div>content</div><div>more</div>
整个字符串从第一个 < 到最后一个 > 都被匹配了。
2. 非贪婪模式(Lazy / Reluctant)
定义
- 非贪婪模式尝试匹配尽可能少的字符,满足整个表达式即可停止匹配。
- 用
*?,+?,??,{n,m}?来表示非贪婪量词。
量词符号(非贪婪)
| 量词 | 含义 |
|---|---|
*? | 0 次或多次,尽量少匹配 |
+? | 1 次或多次,尽量少匹配 |
?? | 0 次或 1 次,尽量少匹配 |
{n,m}? | 匹配 n 到 m 次,尽量少匹配 |
例子
字符串:
"<div>content</div><div>more</div>"
正则:
<.*?>
.*?是非贪婪匹配,尽量少匹配字符。- 匹配结果:
<div>
<div>
匹配到的第一个 <div> 标签结束的位置就停下来。
3. 两者对比总结
| 模式 | 匹配原则 | 示例匹配结果 |
|---|---|---|
| 贪婪模式 | 尽可能多匹配 | <div>content</div><div>more</div> |
| 非贪婪模式 | 尽可能少匹配 | <div>和 <div> |
4. Java 示例代码
public static void greedyVsLazy() {
String text = "<div>content</div><div>more</div>";
// 贪婪模式
Pattern greedy = Pattern.compile("<.*>");
Matcher greedyMatcher = greedy.matcher(text);
if (greedyMatcher.find()) {
System.out.println("贪婪匹配: " + greedyMatcher.group());
}
// 非贪婪模式
Pattern lazy = Pattern.compile("<.*?>");
Matcher lazyMatcher = lazy.matcher(text);
System.out.println("非贪婪匹配:");
while (lazyMatcher.find()) {
System.out.println(lazyMatcher.group());
}
}
输出结果
贪婪匹配: <div>content</div><div>more</div>
非贪婪匹配:
<div>
</div>
<div>
</div>
5. 什么时候用哪种?
- 贪婪模式:适合你想匹配尽可能长的字符串,比如抓取从某个标记开始到最后的所有内容。
- 非贪婪模式:适合你想匹配最近的结束符,比如匹配HTML标签、括号内容等。
✅ 八、常见正则表达式示例(Java用)
| 目的 | 正则表达式 |
|---|---|
| 手机号(中国) | ^1[3-9]\\d{9}$ |
| 邮箱地址 | ^[\\w.-]+@[\\w.-]+\\.\\w{2,}$ |
| 身份证号码 | ^\\d{15}(\\d{2}[0-9Xx])?$ |
| IP 地址 | ^(\\d{1,3}\\.){3}\\d{1,3}$ |
| 密码强度检测 | ^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$ |
| 日期格式 | ^\\d{4}-\\d{2}-\\d{2}$ |
✅ 九、Java 正则常用 API 详解
Java 正则相关的核心类主要在 java.util.regex 包里,主要有两个:
Pattern—— 编译正则表达式,生成模式对象Matcher—— 对目标字符串进行匹配操作
1. Pattern 类
| 方法 | 说明 |
|---|---|
Pattern.compile(String regex) | 编译正则表达式,返回 Pattern对象 |
Pattern.compile(String regex, int flags) | 编译正则表达式,并带有修饰符(如忽略大小写等) |
pattern.matcher(CharSequence input) | 创建一个匹配器 Matcher,用来对输入字符串进行匹配 |
pattern.split(CharSequence input) | 按正则表达式分割字符串,返回字符串数组 |
2. Matcher 类
| 方法 | 说明 |
|---|---|
matcher.find() | 查找下一个匹配,返回 true或 false |
matcher.matches() | 判断整个字符串是否完全匹配正则表达式 |
matcher.group() | 获取整个匹配的子串(等价于 group(0)) |
matcher.group(int group) | 获取第 group个捕获组匹配到的子串 |
matcher.start() | 返回当前匹配的开始索引 |
matcher.end() | 返回当前匹配的结束索引 |
matcher.replaceAll(String replacement) | 将所有匹配替换为给定字符串 |
matcher.replaceFirst(String replacement) | 将第一个匹配替换为给定字符串 |
matcher.reset() | 重置匹配器,重新开始匹配 |
3. 例子演示
package com.omy.regex;
import jdk.nashorn.internal.ir.IfNode;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexExample1 {
//普通示例
static void func1() {
String input = "abc123xyz456";
//匹配连续出现1次或多次的数字
String regex = "\\d+";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
System.out.println("匹配结果: " + matcher.group());
}
}
//分组示例
static void func2() {
String text = "2025-05-22";
Pattern pattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
System.out.println(matcher.group()); // 输出整个匹配: 2025-05-22
System.out.println(matcher.group(1)); // 输出第1个捕获组: 2025(年)
System.out.println(matcher.group(2)); // 输出第2个捕获组: 05(月份)
System.out.println(matcher.group(3)); // 输出第3个捕获组: 22(日)
}
}
//分组示例
static void func3() {
String input = "hello hello";
//分组示例:重复的单词,中间有一个空白字符。
String regex = "(\\w+)\\s\\1";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
System.out.println("匹配结果: " + matcher.group());
System.out.println("匹配第1组: " + matcher.group(1));
// System.out.println("匹配第2组: " + matcher.group(2)); // \1 不能作为分组
}
}
//边界匹配符 \b 示例
static void func4() {
String str = "testing bringing resting";
/**
* (\\w+) :
* 捕获一个或多个字母、数字或下划线(即单词字符),这是第 1 个捕获组。它会匹配单词中 "ing" 之前的部分。
* (?=ing\\b) :
* 正向预测先行断言(Lookahead)
* 表示后面紧跟着 "ing" 并且后面是单词边界(\\b),但“ing”不属于捕获内容。
* 换句话说,匹配的是紧接着后面有 "ing" 的单词前半部分。
*/
Pattern pattern = Pattern.compile("(\\w+)(?=ing\\b)");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group(1));
}
}
//只匹配以 "abc" 开头的字符串(后面可以跟任意内容)
static void func5() {
String regex = "^abc.*";
String[] testStrings = {"abc123", "abcdef", "aabc", "123abc"};
for (String s : testStrings) {
if (s.matches(regex)) {
System.out.println(s + " 匹配");
} else {
System.out.println(s + " 不匹配");
}
}
}
//综合案例
static void func6() {
String text = "Email: test@example.com and admin@abc.com";
String regex = "(\\w+)@(\\w+\\.\\w+)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println("邮箱完整地址: " + matcher.group()); // 整个邮箱地址
System.out.println("邮箱用户名: " + matcher.group(1)); // 捕获组1:用户名
System.out.println("邮箱域名: " + matcher.group(2)); // 捕获组2:域名
System.out.println("匹配结果在原串中起始位置: " + matcher.start() + "-" + matcher.end());
System.out.println();
System.out.println();
}
}
public static void greedyVsLazy() {
String text = "<div>content</div><div>more</div>";
// 贪婪模式
Pattern greedy = Pattern.compile("<.*>");
Matcher greedyMatcher = greedy.matcher(text);
if (greedyMatcher.find()) {
System.out.println("贪婪匹配: " + greedyMatcher.group());
}
// 非贪婪模式
Pattern lazy = Pattern.compile("<.*?>");
Matcher lazyMatcher = lazy.matcher(text);
System.out.println("非贪婪匹配:");
while (lazyMatcher.find()) {
System.out.println(lazyMatcher.group());
}
}
public static void main(String[] args) {
func1();
System.out.println("====================分割线======================");
func2();
System.out.println("====================分割线======================");
func3();
System.out.println("====================分割线======================");
func4();
System.out.println("====================分割线======================");
func5();
System.out.println("====================分割线======================");
func6();
System.out.println("====================分割线======================");
greedyVsLazy();
}
}
4. 常用修饰符(flags)
Pattern.CASE_INSENSITIVE:忽略大小写Pattern.MULTILINE:多行模式,^和$匹配每行的开始和结束Pattern.DOTALL:使.匹配换行符Pattern.UNICODE_CASE:使用 Unicode 规则忽略大小写
✅ 十、正则表达式性能优化指南正则表达式性能优化指南
1. 减少回溯(Backtracking)
- 避免使用“贪婪量词 + 可选重复”叠加,例如
(a+)+或(.*)*,这些容易导致大量回溯,严重影响性能。 - 使用 非贪婪量词 或限定重复次数,避免不确定性的叠加。
- 示例:
- 避免:
(.*a)* - 推荐:明确限定重复次数,或者拆分成多个步骤。
- 避免:
2. 使用合理的量词
- 避免使用
.*或.+过于宽泛匹配,尤其是在多行内容或大文本中。 - 通过更具体的字符集替代
.,如[a-zA-Z0-9]等,提高匹配速度。
3. 使用原子组(Atomic Grouping)和禁止回溯(Possessive Quantifiers)
- Java 支持原子组
(?>...),避免回溯:
(?>a+)b
- 还有 占有量词,例如
a++、.*+,匹配时不允许回溯,减少性能损耗。
4. 提前排除不匹配字符
- 使用字符类和限定符,避免宽泛匹配。
- 例如匹配数字,不用
.,用\d;匹配特定范围,使用[a-z]。
5. 合理使用断言
- 断言(正向/反向预测)不会消耗匹配结果,但滥用复杂断言可能导致性能下降。
- 保持断言简单和明确。
6. 预编译正则表达式
- 尽量 复用
**Pattern**对象,避免在循环内反复编译正则。 Pattern.compile()是有成本的,复用能提高性能。
7. 使用 matches() 与 find() 的区别
matches()匹配整个字符串,适合验证格式,耗时相对较大。find()匹配子串,适合查找,通常更高效。
8. 避免过度嵌套分组
- 多层分组增加匹配复杂度,合理简化分组结构。
7万+

被折叠的 条评论
为什么被折叠?



