编程与开发 · 第 8 期 · 第一季收官

自己写个正则引擎,NFA 到 DFA

Thompson 算法 · regex engine internals

正则不是魔法,是状态机。Thompson 算法把正则编译成 NFA(非确定有限自动机),NFA 能跑但每步要试多条路;子集构造把 NFA 转成 DFA(确定有限自动机),DFA 每步只有一条路,快但占内存。理解这条链路,正则引擎就不神秘了。
⏱ 约 11 分钟 🎯 用正则但不懂它内部的人 📦 源:build-your-own-x §4

01一个反共识:正则是状态机,不是魔法

写 a(b|c)*d 能匹配 "abcbcd",看起来像魔法。其实它被编译成了一台状态机,匹配就是在这台机器上走。

正则 → Thompson → NFA → 子集构造 → DFA → 匹配

NFA(非确定):一个状态遇到一个字符可能跳到多个状态,要同时维护"当前可能在哪几个状态"。好建、好理解,但匹配时要跟踪状态集合。

DFA(确定):一个状态遇到一个字符只跳到一个状态,匹配就是顺着一个指针走。快,但状态数可能爆炸(指数膨胀)。工程上常直接用 NFA(够用),或对常用正则预编译成 DFA(求快)。

正则是状态机,
不是魔法。
灏天文库 · 编程与开发 P.24

02正则匹配演示:看状态怎么走

输入正则和字符串(或点示例),点"匹配",看结果和匹配过程。

🔤 正则匹配演示
输入正则 + 字符串,看是否匹配。用简化的 NFA 模拟。
← 输入后点匹配

03为什么有些正则"灾难性"慢

你大概遇到过:一个正则跑着跑着卡死了。原因是回溯。

很多正则引擎用"回溯"实现 NFA:遇到分支先试一条路,走不通退回来试另一条。大多数情况没问题,但遇到 (a+)+b 这种嵌套量词 + 不匹配的字符串,回溯路径数指数爆炸——O(2ⁿ)。

解法是用 Thompson NFA(不回溯,同时跟踪所有可能状态)或预编译成 DFA——它们是 O(nm) 的,不会指数爆炸。这就是为什么 Rust 的 regex 引擎不会"灾难性回溯"——它用 Thompson 算法,而很多老引擎(PCRE、Python re)用回溯。选引擎要看它是不是回溯型,对用户输入的正则尤其要小心。

回溯型正则会爆炸,
Thompson 型不会。
灏天文库 · 编程与开发 P.25

04带走这套清单

✅ 正则引擎 6 条可执行规则

  1. 正则是状态机:Thompson 编译成 NFA,子集构造转 DFA。
  2. NFA 好建好理解,DFA 快但可能状态爆炸,按需选。
  3. 警惕回溯型引擎:嵌套量词 + 不匹配字符串会指数爆炸。
  4. 用户输入的正则用 Thompson 型引擎:不回溯,不会卡死。
  5. 从零写一遍:Thompson 构造 + NFA 模拟,比看文档懂 10 倍。
  6. 不追求兼容 PCRE:目标是理解状态机,不是替代标准库。
造过正则引擎,
正则就不神秘了。
灏天文库 · 编程与开发 P.26