1. 为什么要认真学正则表达式:它解决的是“文本结构的识别”问题
很多年前我第一次被正则表达式弄得头大,是因为一条日志解析的需求:从一行混合了时间、级别、模块和消息的日志里,把各个字段都拎出来。当时我的第一反应是写一串 split,结果遇到消息里也有分隔符的情况立刻崩了。后来才意识到,凡是“文本有一定的结构规律,但又不完全规整”的场景,正则表达式(Regular Expression,简称 RE)都是最顺手的那把刀。
正则表达式本质上是用来描述“字符串集合”的一种模式语言。你给它一个模式,它就在目标文本里寻找符合该模式的子串。它不像编程语言那样需要定义变量和流程控制,而是用一套元字符体系直接表达“这里是一个数字、那里是任意三个字符、这个部分可以出现零次或多次”这类规则。
这个能力意味着什么呢?几乎所有跟文本处理沾边的场景都用得上:表单校验(邮箱、手机号、IP地址)、日志分析(从海量日志里提取错误码和耗时)、代码编辑器的批量替换、爬虫里的信息抽取、数据库里的模糊匹配优化,甚至你每天用的搜索引擎,背后都有正则思想的影子。
这篇文章适合谁?如果你已经写过一点代码,但看到正则就头疼,或者你之前只会照抄网上的正则,却不知道它为什么这么写、为什么在某个场景下失灵,那这篇内容就是为你准备的。我会从正则引擎的工作原理讲到具体语法,再给几个可以立刻上手的实战案例,最后把最容易踩的坑一起捋一遍。这几件事弄明白,正则就不再是“玄学”了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 正则引擎的工作方式:先弄懂“为什么”,再背语法
2.1 从“自动机”理论说起:NFA 与 DFA
正则表达式并不是一堆规则的简单拼接,它背后有计算机科学里“自动机”理论的支持。简单来说,一个正则表达式可以被编译成一个“状态机”,状态机会逐字符地读取输入文本,并根据当前状态决定是否接受这个字符、跳转到哪个新状态。
状态机有两种常见类型:NFA(非确定有限自动机)和 DFA(确定有限自动机)。大多数编程语言(Python、Java、JavaScript、C# 等)内置的正则引擎都是 NFA 类型的,比如 PCRE、.NET 的 Regex 类、Python 的 re 模块。NFA 的特点是“回溯”,即匹配失败后会回退到之前的分叉点,尝试另一条路径。DFA 则没有回溯,它在每个状态下对每个字符都只有一个确定去向,匹配过程是线性的。
这里有个很关键的实际影响:NFA 支持反向引用、环视等高级特性,表达能力强,但遇到某些模式可能会发生灾难性回溯;DFA 性能稳定,但功能受限,支持不了反向引用。所以现实里你拿到的“正则”,绝大多数都是 NFA 引擎。理解了这一点,你就明白为什么同样的正则,在不同语言里表现可能不同,也明白为什么某些正则会在极端输入下把 CPU 打满。
2.2 为什么“越通用越慢”:回溯机制带来的性能分水岭
NFA 的正则引擎在做匹配时,如果模式里有多个分支或者量词带有多义性,引擎会先尝试一种方式,失败后再回退尝试另一种。这个“回退”就是回溯。大多数情况下回溯没问题,但模式写得不好,回溯次数会随输入长度指数增长。
经典的例子是 (a+)+$ 去匹配一串 a 后面再加一个不是 a 的字符。(a+) 本身可以贪婪地吃掉很多个 a,外层 + 又允许“更多组”出现,于是引擎会在“外层分组到底分成几组”这件事上反复试探。输入越长,排列组合越多,最终卡死整个进程。这在安全领域叫 ReDoS(正则拒绝服务攻击),攻击者可以利用这种有问题的正则让服务瘫痪。
实际经验是:如果一条正则的执行时间会随输入长度的增加而“肉眼可见地变慢”,那多半是回溯过深了。解决办法不是“不用正则”,而是学会写“有界”的量词、避免嵌套量词,以及尽量用字符类而不是 . 加量词来限定范围。具体参考模式后面我会专门展开。
3. 正则核心语法拆解:从元字符到高级构造
3.1 最小工具箱:字符、字符类与量词
正则的基本组成单位是
