正则表达式详解
【广告】微信小程序 - “两步动态验证”
- 兼容谷歌验证码 :无缝支持Google Authenticator等标准TOTP验证器,通用各类平台账号
- 云端加密备份 :密钥数据端到端加密上传云端,换机不丢失,安全又便捷
- API快速集成 :提供开放API,轻松对接各类应用系统,实现自动化验证码获取
- 多端共享:基于微信小程度,可同时在手机,PC端共同使用,一键复制
- 扫描二维码试用,或微信小程序搜索“两步动态验证”

正则表达式教程
前言
- 本文内容略长,几乎涵盖了正则表达式的全部内容。建议收藏后细细阅读。并且今后可能需要反复查阅
- 文中的语法规则是通用的,但是对于转义使用的斜线在不同的语言中可能会有所不同,比如java中是 \. 表示转义后的 .
- 在不同的语言或工具中正则的功能支持可能会有所不同,比如某些工具里可能会不支持反向引用等功能
- 正则表达式的性能通常都不会太高,相同功能的表达式可以有多种写法,会极大的影响性能,也不推荐写过于复杂的表达式,难以理解,不好维护,不推荐在代码中大量使用
- 在面向字符串编程时,正则表达式可以极大的提高处理速度,部分场景下搭配VLOOKUP,grep等命令,效率更高,推荐在这种场景下使用
1. 正则表达式规则
1.1 普通字符
在正则表达式的世界里,最直接的就是普通字符——字母(a-z, A-Z)、数字(0-9)、汉字、下划线(_),以及那些没有特殊定义的普通标点符号。这些字符就像"所见即所得"的老黄牛,在匹配时只认自己的"孪生兄弟"。
举个例子:当你写了个最简单的表达式 c,让它去字符串 abcde 里找朋友,它会一眼就认出第三个位置的 c,匹配成功!
再看:如果你写了表达式 bcd,它就会组成一个"三人小组",在 abcde 里找到从第2个位置开始的 b、c、d 这三个连续的兄弟,手拉手一起被匹配出来。
小贴士:不同编程语言里字符串的索引可能从0开始,也可能从1开始,具体位置数字别太纠结,理解概念更重要!
1.2 转义字符 - 给特殊字符"脱魔法"的咒语
在正则的魔法世界里,有些字符自带特殊能力(比如^、$、.等),如果我们想让它们变回普通字符,就需要使用\这个"脱魔法咒语"。另外,对于一些看不见摸不着但确实存在的字符,我们也需要用转义序列来表示。
1.2.1 常用转义字符表
| 表达式 | 可匹配 |
|---|---|
\r, \n |
回车符和换行符(控制字符) |
\t |
制表符(Tab键产生的字符) |
\\ |
代表反斜杠\本身(需要双重转义) |
1.2.2 特殊符号的转义
那些在正则中有特殊含义的标点符号,也可以通过转义来匹配它们本身:
| 表达式 | 可匹配 |
|---|---|
\^ |
匹配 ^ 符号本身 |
\$ |
匹配 $ 符号本身 |
\. |
匹配小数点 . 本身 |
小贴士:转义后的字符就像被解除了魔法,它们的匹配行为和普通字符一样,只匹配与之完全相同的字符。
1.2.3 实例演示
表达式 \$d 去匹配字符串 abc$de 时,它会找到 $d 这个组合,因为 \$ 已经被"解除魔法",只能匹配普通的 $ 符号了。
1.3 万能匹配符 - 正则中的"变形金刚"
正则表达式提供了一些强大的"变形金刚",它们可以变身成多种字符中的任意一个。不过要记住,它们每次只能变身为一个字符,就像扑克牌里的大小王,一次只能代替一张牌。
1.3.1 常用万能匹配符
| 表达式 | 可匹配 |
|---|---|
\d |
数字变形金刚:匹配任意一个数字(0-9) |
\w |
单词变形金刚:匹配任意一个字母、数字或下划线(A-Z, a-z, 0-9, _) |
\s |
空白变形金刚:匹配任意一种空白字符(空格、制表符、换行符等) |
. |
超级变形金刚:匹配除换行符(\n)以外的任何一个字符 |
1.3.2 实例解析
-
当用
\d\d去匹配abc123时,它就像派出了两个数字变形金刚,分别抓住了字符串中最前面的两个数字1和2,匹配成功! -
表达式
a.\d则是一个组合队伍:一个固定的a,加上一个超级变形金刚(可以变成任何字符),再加上一个数字变形金刚。当它们遇到aaa100时,中间的超级变形金刚变成了a,最后一个抓住了1,于是匹配到了aa1。
1.4 字符集合 - 自定义你的匹配规则
如果内置的变形金刚不够用,正则还允许我们创建自己的"字符战队"!使用方括号 [ ] 可以圈出一群字符,它们会像一个团队一样工作,匹配时只要遇到其中任何一个就算成功。
1.4.1 字符集合的创建方法
| 表达式 | 可匹配 |
|---|---|
[ab5@] |
自定义战队:匹配 a 或 b 或 5 或 @ 中的任意一个 |
[^abc] |
反选战队:匹配除了 a、b、c 以外的任意一个字符(注意 ^ 在方括号内表示"排除") |
[f-k] |
范围战队:匹配从 f 到 k 之间的任意一个字母(f, g, h, i, j, k) |
[^A-F0-3] |
复杂反选:匹配除了 A-F 字母和 0-3 数字以外的任意一个字符 |
小技巧:使用连字符
-可以快速定义一个字符范围,比一个个列出所有字符要高效得多!
1.4.2 实战演示
-
当表达式
[bcd][bcd]去挑战字符串abc123时,两个字符战队协同作战:第一个战队匹配到了b,第二个战队匹配到了c,于是成功捕获了bc。 -
表达式
[^abc]就像是一个"反abc联盟",在abc123中,它会跳过前面的a、b、c,直接找到第一个不是这三个字母的字符1。
1.5 修饰匹配次数的特殊符号
前面章节中讲到的表达式,无论是只能匹配一种字符的表达式,还是可以匹配多种字符其中任意一个的表达式,都只能匹配一次。如果使用表达式再加上修饰匹配次数的特殊符号,那么不用重复书写表达式就可以重复匹配。
使用方法是:次数修饰放在被修饰的表达式后边。比如:[bcd][bcd] 可以写成 [bcd]{2}.
| 表达式 | 作用 |
|---|---|
表达式重复n次,比如:\w{2} 相当于 \w\w;a{5} 相当于 aaaaa |
|
表达式至少重复m次,最多重复n次,比如:ba{1,3}可以匹配 ba或baa或baaa |
|
表达式至少重复m次,比如:\w\d{2,}可以匹配 a12,_456,M12344... |
|
| ? | 匹配表达式0次或者1次,相当于 {0,1},比如:a[cd]?可以匹配 a,ac,ad |
| + | 表达式至少出现1次,相当于 {1,},比如:a+b可以匹配 ab,aab,aaab... |
| * | 表达式不出现或出现任意次,相当于 {0,},比如:\^*b可以匹配 b,^^^b... |
举例1:表达式 \d+\.?\d* 在匹配 It costs $12.5 时,匹配的结果是:成功;匹配到的内容是:12.5;匹配到的位置是:开始于10,结束于14。
举例2:表达式 go{2,8}gle 在匹配 Ads by goooooogle 时,匹配的结果是:成功;匹配到的内容是:goooooogle;匹配到的位置是:开始于7,结束于17。
1.6 位置与关系符号 - 正则表达式的交通信号灯和连接器
正则表达式里有一些特殊符号,它们不匹配具体字符,而是匹配字符之间的"位置"或者定义表达式之间的"关系"。理解它们就像学会看交通信号灯一样重要!
1.6.1 位置定位符号
这些符号就像GPS定位器,能帮你精确找到字符串中的特定位置:
| 表达式 | 作用 |
|---|---|
^ |
字符串起点标记:匹配字符串的最开始位置,本身不匹配任何字符 |
$ |
字符串终点标记:匹配字符串的最末尾位置,本身不匹配任何字符 |
\b |
单词边界:匹配单词和非单词字符之间的位置,就像单词之间的无形分隔线 |
小贴士:位置符号的特点是"看不见摸不着",它们不消耗任何字符,只定位位置!
1.6.2 实例解析:位置符号的用法
-
^符号实例:表达式^aaa就像个门卫,只检查字符串最前面是不是aaa。当它遇到xxx aaa xxx时,会说"不对,你前面还有东西!";但遇到aaa xxx xxx时,就会高兴地说"正合我意!" -
$符号实例:表达式aaa$则像个守门员,只检查字符串最后面是不是aaa。看到xxx aaa xxx会摇头,看到xxx xxx aaa才会点头放行。 -
\b符号实例:- 表达式
\bend\b就像在说:"我要找一个完整的单词 'end',它前后都不能是字母、数字或下划线!" 所以在weekend,endfor,end中,只有最后那个独立的end会被匹配到。 \b就像一个隐形的边界墙,一边必须是单词字符(\w),另一边必须是非单词字符。
- 表达式
1.6.3 表达式关系符号
除了定位,正则还有连接表达式的"逻辑关系符":
| 表达式 | 作用 |
|---|---|
| ` | ` |
() |
分组与捕获: (1) 将多个字符包装成一个整体,就像数学中的括号 (2) 捕获匹配的内容,方便后续单独提取使用 |
1.6.4 实例解析:关系符号的用法
-
|符号实例:表达式Tom|Jack就像在说"给我找 Tom,或者 Jack,谁先来算谁"。在I'm Tom, he is Jack中,它会先找到Tom,然后继续寻找下一个匹配的Jack。 -
()符号实例:- 表达式
(go\s*)+中的括号把go和可能的空格包装成一个整体,然后用+修饰这个整体,表示"这个组合要出现一次或多次"。所以在Let's go go go!中,它能匹配到go go go。 - 表达式
¥(\d+\.?\d*)则更聪明:它匹配完整的人民币金额¥20.5,同时通过括号把数字部分20.5单独"捕获"出来,方便我们后续只提取数字进行计算或处理。
- 表达式
2. 正则表达式中的一些高级规则
2.1 贪婪与非贪婪匹配
在正则表达式中,匹配次数修饰符(如 {m,n}, {m,}, ?, *, +)在默认情况下会尽可能多地匹配字符,这种行为称为"贪婪模式"。
2.1.1 贪婪模式
贪婪模式下,正则表达式会尽可能匹配更多的字符,直到无法继续匹配为止。来看几个实际例子:
| 表达式 | 匹配文本 | 匹配结果说明 |
|---|---|---|
(d)(\w+) |
dxxxdxxxd |
\w+ 匹配第一个 d 之后的所有字符 xxxdxxxd |
(d)(\w+)(d) |
dxxxdxxxd |
\w+ 匹配第一个 d 和最后一个 d 之间的所有字符 xxxdxxx。注意:虽然 \w+ 本身可以匹配最后一个 d,但为了让整个表达式成功匹配,它会"让出"最后一个 d |
所有带 *、+ 和 {m,n} 的表达式默认都是贪婪的,会尽可能多地匹配符合规则的字符。
2.1.2 非贪婪模式
在匹配次数修饰符后添加 ? 号,可以启用非贪婪模式(也称为"勉强模式")。非贪婪模式会尽可能少地匹配字符,但仍确保整个表达式能够匹配成功。
| 表达式 | 匹配文本 | 匹配结果说明 |
|---|---|---|
(d)(\w+?) |
dxxxdxxxd |
\w+? 只匹配第一个 x,因为它尝试尽可能少地匹配 |
(d)(\w+?)(d) |
dxxxdxxxd |
\w+? 匹配 xxx,因为需要确保后面的 d 能够匹配,从而让整个表达式成功 |
2.1.3 实际应用示例
在处理HTML或XML等嵌套结构时,贪婪与非贪婪模式的区别尤为明显:
-
贪婪模式示例:表达式
<td>(.*)</td>匹配<td><p>aa</p></td> <td><p>bb</p></td>时,会匹配从第一个<td>到最后一个</td>的整个字符串<td><p>aa</p></td> <td><p>bb</p></td>。 -
非贪婪模式示例:表达式
<td>(.*?)</td>匹配相同字符串时,只会匹配第一个完整的<td>...</td>块<td><p>aa</p></td>。再次匹配时,可以获取第二个<td><p>bb</p></td>。
小贴士:处理包含多个重复模式的文本时,非贪婪模式通常更适合提取每个独立的匹配项,而不是将整个文本作为一个大的匹配结果。
2.2 反向引用 \1, \2...
反向引用是正则表达式中的一个强大功能,它允许我们在表达式中引用之前匹配到的内容。
2.2.1 反向引用的基本概念
当使用小括号 ( ) 包裹表达式时,正则引擎会自动记录括号内匹配到的文本。这些记录可以在表达式后续部分通过 \数字 的形式引用:
\1引用第1对括号内匹配到的内容\2引用第2对括号内匹配到的内容- 以此类推...
括号的编号规则是按照左括号 ( 出现的顺序来确定的,即使存在嵌套括号也是如此。
2.2.2 反向引用的实际应用
| 表达式 | 匹配文本 | 匹配结果说明 |
|---|---|---|
| `(' | " | )(.*?)(\1) |
(\w)\1{4,} |
aa bbbb abcdefg ccccc 111121111 999999999 |
匹配同一个字符连续出现至少5次的情况。注意与 \w{5,} 的区别:\w{5,} 可以匹配任意5个或更多字符,而 (\w)\1{4,} 必须是同一个字符重复至少5次 |
| `<(\w+)\s*(\w+(=(' | " | ).*?\4)?\s*)*>.*?</\1> |
反向引用的主要价值在于:它允许我们在表达式中创建动态的匹配规则,特别是当我们需要确保某些文本片段相同时。
2.3 预搜索与反向预搜索
预搜索(也称为零宽断言)是一种特殊的正则表达式功能,它允许我们检查某个位置的前后字符,但不消耗这些字符(即不将它们包含在匹配结果中)。
2.3.1 正向预搜索
正向预搜索检查位置右侧是否满足特定条件:
(?=xxxxx)- 正向前瞻:所在位置右侧必须匹配xxxxx(?!xxxxx)- 负向前瞻:所在位置右侧必须不匹配xxxxx
| 表达式 | 匹配文本 | 匹配结果说明 |
|---|---|---|
| `Windows (?=NT | XP)` | Windows 98, Windows NT, Windows 2000 |
do(?!\w) |
done, do, dog |
匹配后面不是单词字符的 do,因此只匹配单独的 do 而不匹配 done 或 dog 中的 do |
((?!\bstop\b).)+ |
fdjka ljfdl stop fjdsla fdj |
匹配从开头到 stop 单词之前的所有字符 |
2.3.2 反向预搜索
反向预搜索检查位置左侧是否满足特定条件:
(?<=xxxxx)- 正向后瞻:所在位置左侧必须匹配xxxxx(?<!xxxxx)- 负向后瞻:所在位置左侧必须不匹配xxxxx
| 表达式 | 匹配文本 | 匹配结果说明 |
|---|---|---|
(?<=\d{4})\d+(?=\d{4}) |
1234567890123456 |
匹配前后都有4位数字的中间部分(本例中是中间8位数字) |
注意:反向预搜索在某些正则引擎中可能不受支持,如JavaScript的JScript.RegExp。但在许多现代引擎中(如Java、.NET、Python等)都得到了支持。
小贴士:预搜索的价值在于它允许我们创建更精确的匹配条件,而不需要将条件本身包含在最终的匹配结果中,这在提取数据时非常有用。
3. 其他通用规则
除了前面介绍的基础知识,正则表达式还有一些跨引擎通用的重要规则需要掌握。
3.1 字符的特殊表示法
在正则表达式中,可以使用十六进制编码精确指定字符:
| 形式 | 字符范围 | 示例 |
|---|---|---|
\xXX |
编号在 0-255 范围的字符 | 空格可以表示为 \x20 |
\uXXXX |
任何字符(Unicode) | 中文字符可以用其Unicode编码表示 |
3.2 大写字母表示相反意义
在前面学习的特殊字符中,对应的大写形式表示相反的匹配规则:
| 表达式 | 匹配内容 |
|---|---|
\S |
匹配所有非空白字符(\s 的反面) |
\D |
匹配所有非数字字符(\d 的反面) |
\W |
匹配所有非字母、数字、下划线的字符(\w 的反面) |
\B |
匹配非单词边界,即左右两边要么都是 \w,要么都不是 \w |
3.3 需要转义的特殊字符
正则表达式中有许多特殊字符,想要匹配它们本身必须加上反斜杠 \:
| 字符 | 原始含义 | 转义后 |
|---|---|---|
^ |
匹配字符串开始 | \^ |
$ |
匹配字符串结束 | \$ |
( ) |
标记子表达式 | \( 和 \) |
[ ] |
字符集合 | \[ 和 \] |
{ } |
量词 | \{ 和 \} |
. |
匹配任意字符(除换行) | \. |
? |
0次或1次 | \? |
+ |
1次或多次 | \+ |
* |
0次或多次 | \* |
| ` | ` | 或操作 |
小贴士:如果你记不清哪些字符需要转义,一个简单的方法是:将所有非字母数字的字符都转义,这样做不会有坏处。
3.4 非捕获括号
使用 (?:xxxxx) 格式可以创建一个不会被捕获(记录)的子表达式:
| 表达式 | 匹配文本 | 匹配结果说明 |
|---|---|---|
(?:(\w)\1)+ |
a bbccdd efg |
匹配结果是 bbccdd。(?:) 内的内容不会被记录,但内部的 (\w) 仍然会被捕获并可用 \1 引用 |
非捕获括号在不需要反向引用时使用,可以提高正则表达式的执行效率。
3.5 正则表达式的修饰符
不同的正则引擎支持不同的修饰符(也称为标志),以下是最常用的几种以及它们在不同语言和工具中的具体用法:
| 修饰符 | 说明 | JavaScript | Java | Python |
|---|---|---|---|---|
IgnoreCase(忽略大小写) |
匹配时不区分大小写 | i 标志 |
Pattern.CASE_INSENSITIVE |
re.IGNORECASE 或 re.I |
Singleline(单行模式) |
使 . 能匹配换行符 |
无直接对应 (需手动处理) |
Pattern.DOTALL |
re.DOTALL 或 re.S |
Multiline(多行模式) |
使 ^ 和 $ 能匹配每行的开始和结束 |
m 标志 |
Pattern.MULTILINE |
re.MULTILINE 或 re.M |
Global(全局匹配) |
查找所有匹配项而非仅第一个 | g 标志 |
无直接对应 (需手动循环) |
默认全局 (通过 findall(), finditer() 等) |
3.5.1 JavaScript 中的修饰符使用
// 忽略大小写 (i)
const regex1 = /hello/i;
console.log(regex1.test("Hello")); // true
console.log(regex1.test("HELLO")); // true
// 多行模式 (m)
const regex2 = /^start/gm; // 结合全局匹配
const text = "start line\nstart again\nnot start";
const matches = text.match(regex2); // ["start", "start"]
// 全局匹配 (g)
const regex3 = /\d+/g;
const numbers = "123 abc 456 def 789";
console.log(numbers.match(regex3)); // ["123", "456", "789"]
3.5.2 Java 中的修饰符使用
import java.util.regex.*;
public class RegexExample {
public static void main(String[] args) {
// 忽略大小写
Pattern pattern1 = Pattern.compile("hello", Pattern.CASE_INSENSITIVE);
Matcher matcher1 = pattern1.matcher("HELLO World");
System.out.println(matcher1.find()); // true
// 多行模式
Pattern pattern2 = Pattern.compile("^start", Pattern.MULTILINE);
Matcher matcher2 = pattern2.matcher("start line\nstart again");
while (matcher2.find()) {
System.out.println("Found: " + matcher2.group()); // 输出两次匹配
}
// 单行模式 (DOTALL)
Pattern pattern3 = Pattern.compile(".*", Pattern.DOTALL);
Matcher matcher3 = pattern3.matcher("line1\nline2");
if (matcher3.find()) {
System.out.println(matcher3.group()); // 匹配包括换行符的所有内容
}
// 组合使用多个标志
Pattern pattern4 = Pattern.compile("^hello.*world$",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
}
}
3.5.3 常用文本编辑器中的修饰符
VSCode:
- 忽略大小写:点击搜索框中的
Aa图标或按Alt+C - 多行匹配:在搜索时使用
^和$会自动识别每行 - 正则表达式模式:点击
.*图标或按Alt+R
Sublime Text:
- 忽略大小写:点击搜索框中的
Aa图标 - 正则表达式:点击
.*图标 - 可以在搜索框中直接使用修饰符,如
(?i)hello表示忽略大小写
Notepad++:
- 正则表达式模式:在搜索模式中选择"正则表达式"
- 忽略大小写:搜索框下方勾选"匹配大小写"选项(取消勾选表示忽略大小写)
3.5.4 通用注意事项
- 修饰符组合:大多数语言支持同时使用多个修饰符
- 性能影响:某些修饰符(如忽略大小写)可能会对性能产生一定影响
- 替代语法:有些语言支持在正则表达式内部使用内联修饰符,如
(?i)hello表示忽略大小写匹配 "hello" - 不同名称:不同语言可能使用不同的名称表示相同功能,例如 JavaScript 的
i标志等同于 Java 的CASE_INSENSITIVE
4. 实用技巧与注意事项
4.1 匹配完整字符串
如果需要确保匹配的是整个字符串而不是其中一部分,可以使用 ^ 和 $ 来限定范围:
^\d+$ # 匹配只包含数字的完整字符串
4.2 匹配完整单词
要匹配完整的单词,避免匹配单词的一部分,可以使用单词边界 \b:
\b(if|while|else|void|int)\b # 匹配编程语言中的关键字
4.3 避免匹配空字符串
尽量避免编写能匹配空字符串的表达式,这可能导致意外的匹配结果:
不好的写法:
\d*\.?\d* # 可以匹配空字符串
更好的写法:
\d+\.?\d*|\.\d+ # 至少需要有一个数字
4.4 避免潜在的死循环
如果括号内的子表达式可以匹配空字符串,并且这个括号可以无限次重复,可能会导致匹配过程进入无限循环:
(a*)+ # 危险:内部 `a*` 可以匹配空字符串,外部 `+` 可以无限重复
虽然现代正则引擎通常能避免这种情况,但最好还是避免编写这样的表达式。
4.5 合理使用 | 操作符
使用 | 操作符时,确保左右两边的表达式对同一字符不会同时匹配,这样可以避免因为顺序不同而得到不同结果。
如果你有任何问题,或者想分享你使用正则表达式的有趣经历,欢迎在评论区留言讨论!
如果大家觉得这篇文章对你有帮助,别忘了收藏点赞分享哦!感谢支持!


浙公网安备 33010602011771号