如果我的文章对您有帮助,麻烦回复一个赞,给我坚持下去的动力

正则表达式详解

【广告】微信小程序 - “两步动态验证”

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

图片

正则表达式教程

前言

  • 本文内容略长,几乎涵盖了正则表达式的全部内容。建议收藏后细细阅读。并且今后可能需要反复查阅
  • 文中的语法规则是通用的,但是对于转义使用的斜线在不同的语言中可能会有所不同,比如java中是 \. 表示转义后的 .
  • 在不同的语言或工具中正则的功能支持可能会有所不同,比如某些工具里可能会不支持反向引用等功能
  • 正则表达式的性能通常都不会太高,相同功能的表达式可以有多种写法,会极大的影响性能,也不推荐写过于复杂的表达式,难以理解,不好维护,不推荐在代码中大量使用
  • 在面向字符串编程时,正则表达式可以极大的提高处理速度,部分场景下搭配VLOOKUP,grep等命令,效率更高,推荐在这种场景下使用

1. 正则表达式规则

1.1 普通字符

在正则表达式的世界里,最直接的就是普通字符——字母(a-z, A-Z)、数字(0-9)、汉字、下划线(_),以及那些没有特殊定义的普通标点符号。这些字符就像"所见即所得"的老黄牛,在匹配时只认自己的"孪生兄弟"。

举个例子:当你写了个最简单的表达式 c,让它去字符串 abcde 里找朋友,它会一眼就认出第三个位置的 c,匹配成功!

再看:如果你写了表达式 bcd,它就会组成一个"三人小组",在 abcde 里找到从第2个位置开始的 bcd 这三个连续的兄弟,手拉手一起被匹配出来。

小贴士:不同编程语言里字符串的索引可能从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 实例解析

  1. 当用 \d\d 去匹配 abc123 时,它就像派出了两个数字变形金刚,分别抓住了字符串中最前面的两个数字 12,匹配成功!

  2. 表达式 a.\d 则是一个组合队伍:一个固定的 a,加上一个超级变形金刚(可以变成任何字符),再加上一个数字变形金刚。当它们遇到 aaa100 时,中间的超级变形金刚变成了 a,最后一个抓住了 1,于是匹配到了 aa1


1.4 字符集合 - 自定义你的匹配规则

如果内置的变形金刚不够用,正则还允许我们创建自己的"字符战队"!使用方括号 [ ] 可以圈出一群字符,它们会像一个团队一样工作,匹配时只要遇到其中任何一个就算成功。

1.4.1 字符集合的创建方法

表达式 可匹配
[ab5@] 自定义战队:匹配 ab5@ 中的任意一个
[^abc] 反选战队:匹配除了 abc 以外的任意一个字符(注意 ^ 在方括号内表示"排除")
[f-k] 范围战队:匹配从 fk 之间的任意一个字母(f, g, h, i, j, k
[^A-F0-3] 复杂反选:匹配除了 A-F 字母和 0-3 数字以外的任意一个字符

小技巧:使用连字符 - 可以快速定义一个字符范围,比一个个列出所有字符要高效得多!

1.4.2 实战演示

  1. 当表达式 [bcd][bcd] 去挑战字符串 abc123 时,两个字符战队协同作战:第一个战队匹配到了 b,第二个战队匹配到了 c,于是成功捕获了 bc

  2. 表达式 [^abc] 就像是一个"反abc联盟",在 abc123 中,它会跳过前面的 abc,直接找到第一个不是这三个字母的字符 1


1.5 修饰匹配次数的特殊符号

前面章节中讲到的表达式,无论是只能匹配一种字符的表达式,还是可以匹配多种字符其中任意一个的表达式,都只能匹配一次。如果使用表达式再加上修饰匹配次数的特殊符号,那么不用重复书写表达式就可以重复匹配。

使用方法是:次数修饰放在被修饰的表达式后边。比如:[bcd][bcd] 可以写成 [bcd]{2}.

表达式 作用
表达式重复n次,比如:\w{2} 相当于 \w\wa{5} 相当于 aaaaa
表达式至少重复m次,最多重复n次,比如:ba{1,3}可以匹配 babaabaaa
表达式至少重复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 实例解析:位置符号的用法

  1. ^ 符号实例:表达式 ^aaa 就像个门卫,只检查字符串最前面是不是 aaa。当它遇到 xxx aaa xxx 时,会说"不对,你前面还有东西!";但遇到 aaa xxx xxx 时,就会高兴地说"正合我意!"

  2. $ 符号实例:表达式 aaa$ 则像个守门员,只检查字符串最后面是不是 aaa。看到 xxx aaa xxx 会摇头,看到 xxx xxx aaa 才会点头放行。

  3. \b 符号实例

    • 表达式 \bend\b 就像在说:"我要找一个完整的单词 'end',它前后都不能是字母、数字或下划线!" 所以在 weekend,endfor,end 中,只有最后那个独立的 end 会被匹配到。
    • \b 就像一个隐形的边界墙,一边必须是单词字符(\w),另一边必须是非单词字符。

1.6.3 表达式关系符号

除了定位,正则还有连接表达式的"逻辑关系符":

表达式 作用
` `
() 分组与捕获
(1) 将多个字符包装成一个整体,就像数学中的括号
(2) 捕获匹配的内容,方便后续单独提取使用

1.6.4 实例解析:关系符号的用法

  1. | 符号实例:表达式 Tom|Jack 就像在说"给我找 Tom,或者 Jack,谁先来算谁"。在 I'm Tom, he is Jack 中,它会先找到 Tom,然后继续寻找下一个匹配的 Jack

  2. () 符号实例

    • 表达式 (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等嵌套结构时,贪婪与非贪婪模式的区别尤为明显:

  1. 贪婪模式示例:表达式 <td>(.*)</td> 匹配 <td><p>aa</p></td> <td><p>bb</p></td> 时,会匹配从第一个 <td> 到最后一个 </td> 的整个字符串 <td><p>aa</p></td> <td><p>bb</p></td>

  2. 非贪婪模式示例:表达式 <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 而不匹配 donedog 中的 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.IGNORECASEre.I
Singleline
(单行模式)
使 . 能匹配换行符 无直接对应
(需手动处理)
Pattern.DOTALL re.DOTALLre.S
Multiline
(多行模式)
使 ^$ 能匹配每行的开始和结束 m 标志 Pattern.MULTILINE re.MULTILINEre.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 通用注意事项

  1. 修饰符组合:大多数语言支持同时使用多个修饰符
  2. 性能影响:某些修饰符(如忽略大小写)可能会对性能产生一定影响
  3. 替代语法:有些语言支持在正则表达式内部使用内联修饰符,如 (?i)hello 表示忽略大小写匹配 "hello"
  4. 不同名称:不同语言可能使用不同的名称表示相同功能,例如 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 合理使用 | 操作符

使用 | 操作符时,确保左右两边的表达式对同一字符不会同时匹配,这样可以避免因为顺序不同而得到不同结果。


如果你有任何问题,或者想分享你使用正则表达式的有趣经历,欢迎在评论区留言讨论!
如果大家觉得这篇文章对你有帮助,别忘了收藏点赞分享哦!感谢支持!

QQ20251121-163914

posted @ 2025-11-21 16:44  无所事事O_o  阅读(2)  评论(0)    收藏  举报