正则表达式(Regular Expression)是一种用于描述字符模式的语言。核心用途是文本的搜索、匹配和替换。
1. 核心元字符
1.1. 字符匹配
符号 | 说明 | 示例 |
---|---|---|
. | 匹配除换行符外的任意单个字符 | a.c 匹配 abc |
[] | 匹配方括号内的任意单个字符 | [abc] 匹配 a |
[^] | 匹配不在方括号内的任意单个字符 | [^abc] 匹配 d |
\d | 匹配一个数字,等价于 [0-9] | \d 匹配 5 |
\D | 匹配一个非数字,等价于 [^0-9] | \D 匹配 a |
\w | 匹配字母、数字、下划线,等价于 [a-zA-Z0-9_] | \w 匹配 b |
\W | 匹配非字母、数字、下划线 | \W 匹配 * |
\s | 匹配任意空白符(空格、制表符、换行符等) | \s 匹配 |
\S | 匹配任意非空白符 | \S 匹配 x |
1.2. 量词
符号 | 说明 |
---|---|
* | 匹配前一个元素 0 次或多次 |
+ | 匹配前一个元素 1 次或多次 |
? | 匹配前一个元素 0 次或 1 次 |
{n} | 匹配前一个元素恰好 n 次 |
{n,} | 匹配前一个元素至少 n 次 |
{n,m} | 匹配前一个元素 n 到 m 次 |
1.3. 边界与定位
符号 | 说明 |
---|---|
^ | 匹配字符串的开头 |
$ | 匹配字符串的结尾 |
\b | 匹配单词边界(单词字符与非单词字符之间) |
\B | 匹配非单词边界 |
1.4. 分组与分支
符号 | 说明 |
---|---|
() | 分组,将多个元素视为一个整体,并捕获内容 |
| | 或,匹配 | 左边或右边的表达式 |
2. 量词模式
2.1. 贪婪模式(Greedy)
默认模式。尽可能多地匹配。
- 表达式:
a.*b
- 文本:
axbyazb
- 匹配结果:
axbyazb
2.2. 懒惰模式(Lazy)
在量词后加 ?
。尽可能少地匹配。
- 表达式:
a.*?b
- 文本:
axbyazb
- 匹配结果:
axb
2.3. 独占模式(Possessive)
在量词后加 +
(部分引擎支持,如 Java, PCRE)。尽可能多地匹配,且不回溯。用于性能优化。
- 表达式:
a.*+b
- 文本:
axbyazb
- 匹配结果: 匹配失败(因为
.*+
吃掉了所有字符直到结尾,不会为了匹配b
而吐出字符)。
3. 高级构造
3.1. 非捕获组 (?:...)
功能与 ()
相同,但不创建捕获组。在只需要分组功能而不需要引用其内容时使用,性能略好。
(\d{4})-(\d{2})
捕获2024
和06
。(?:\d{4})-(\d{2})
只捕获06
。
3.2. 环视(Lookaround)
只匹配位置,不消耗字符。
- 正向先行断言
(?=...)
- 说明:匹配一个位置,该位置后面能匹配
...
。 - 示例:
Windows(?= 7| 8| 10)
匹配Windows 7
中的Windows
。
- 说明:匹配一个位置,该位置后面能匹配
- 负向先行断言
(?!...)
- 说明:匹配一个位置,该位置后面不能匹配
...
。 - 示例:
\d{3}(?!px)
匹配123em
中的123
,但不匹配456px
中的456
。
- 说明:匹配一个位置,该位置后面不能匹配
- 正向后行断言
(?<=...)
- 说明:匹配一个位置,该位置前面能匹配
...
。 - 示例:
(?<=\$)\d+
匹配$100
中的100
。
- 说明:匹配一个位置,该位置前面能匹配
- 负向后行断言
(?<!...)
- 说明:匹配一个位置,该位置前面不能匹配
...
。 - 示例:
(?<!http:)\/\/\w+
匹配ftp://example.com
中的//example.com
。
- 说明:匹配一个位置,该位置前面不能匹配
4. 性能与陷阱
4.1. 核心问题:回溯(Backtracking)
当一个模式有多种匹配方式时(如 (a|b)*
),正则引擎会尝试所有可能路径,直到找到匹配或穷尽所有可能。这个过程叫回溯。
4.2. 灾难性回溯(Catastrophic Backtracking)
当一个模糊的、嵌套的量词模式遇到不匹配的長字符串时,回溯次数会呈指数级增长,导致 CPU 占用 100%,程序卡死。
- 典型陷阱:
^(a+)+$
- 触发文本:
aaaaaaaaaaaaaaaaaaaaaaaa!
- 原因:引擎为了匹配结尾的
$
,会尝试(a+)
的所有可能的分组组合,数量巨大。
4.3. 避免方法
- 使模式更具体:避免在嵌套量词中使用模糊的表达式。例如,用
^a+(a+)*$
改写可能稍好,但根源问题仍在。 - 使用独占模式或原子组:
^(a++)+$
或^(?>a+)+$
。它们匹配后不回溯,直接切断了指数级增长的可能。 - 减少模糊性:明确知道要匹配什么,而不是让引擎去猜。
5. 实践建议
- 使用工具:使用 Regex101 等在线工具进行测试和调试。它能可视化匹配过程和回溯步骤。
- 避免造轮子:对于校验邮箱、URL 等符合 RFC 标准的复杂字符串,直接使用经过验证的库。手写的简单正则基本无法覆盖所有边界情况。
- 优先可读性:复杂的正则难以维护。必要时,宁愿拆分成多个简单的正则,或用代码逻辑辅助。
发表回复