☰
正则 flags 弄明白:g、i、m、s 到底改了什么,还有两个性能坑
2026/9/28 21:07:08 网站建设 项目流程

写正则这么多年,我发现最坑人的,往往不是正则本身写错了,而是 flags 没配对。同一个正则,换一个 flag,行为天差地别,偏偏很多人根本不看这玩意。

这篇把 g、i、m、s 四个最常用的 flag 讲清楚,再补两个性能上的坑。我自己栽过,写出来免得你再栽一遍。
先说这四个 flag 各自干嘛

  • g(全局):不加它,正则匹配到第一个就停了,你只能拿到一个结果。加了才返回所有非重叠匹配。想"提取所有邮箱"“统计某个词出现几次”,不开 g 你永远只有一条。
  • i(忽略大小写):让字母匹配不分大小写。想同时把Error和error都抓出来,开 i 就完事。
  • m(多行):让^和$从"整个字符串的头尾"变成"每一行的头尾"。逐行处理日志的时候,不开这个,^匹配不到每行开头。
  • s(单行):默认.是不匹配换行符的,开了 s 之后.连换行也吃,适合跨行匹配。

这四个可以自由组合。我见过最多的翻车,就是处理多行日志忘了加 m,结果^死活匹配不到每一行;或者想提取全部匹配,忘了加 g,只拿到第一条还以为正则有 bug。

举个具体例子。一段日志:
Error: timeout
info: start
Error: retry
你想统计有几个 Error,写/Error/不加 g,引擎只返回第一个,你数出来是 1,实际上有俩。加了g才是 2。

贪婪匹配,是结果不对的头号原因

量词*、+默认是贪婪的,能多吃就多吃。比如想提取引号里的内容,写".*",它从第一个引号一路吃到最后一个引号,中间全给你吞了。改成".*?",非贪婪,量词后面加个?,它就"少吃点",碰到第一个引号就停。

这个坑在有多对定界符的文本里特别容易中招,很多人在这里卡一下午。

性能坑一:灾难性回溯

这个是真正要命的。正则引擎回溯起来,复杂度能到指数级。经典例子:
(a+)+$
拿去匹配aaaaaaaaaaaaaaaaaaaaab,前面 20 个 a 后面一个 b,永远匹配不上,但引擎会疯狂回溯,把 CPU 吃满,界面直接卡死。

原理不复杂:a+本身能匹配任意多个 a,外面再套个+,就产生了几何级数的分组方式,引擎每种都要试一遍,最后 b 匹配不上才死心。字符串越长,回溯次数越吓人。

怎么写才不中招?两条:

  1. 别用"嵌套量词",像(a+)+这种。
  2. 用明确的字符类替代.*,能精确就精确,比如\d+别写成.*。

还有一条,很多语言的正则引擎(比如 JavaScript 的 V8)用的是回溯型引擎,天然怕这个;而像 RE2 那种用自动机的引擎就没这毛病,但 JS 里没得选,只能自己注意写法。
性能坑二:别在循环里重复编译

这个偏代码层面,但经常被忽略。正则对象如果每次循环都 new 一遍,会反复编译,白白浪费。应该在循环外面编译好,循环里只做匹配。Python 里用re.compile,Java 里预编译 Pattern,都是一个道理。

最后说下我的调试习惯

正则很多问题是全局性的——贪婪、回溯、边界、flags 组合。这些拿一个实时高亮、能同时放多组样本的工具来看,比在代码里打断点直观得多。我的流程是:先在星点这个在线测试器里把正则和样本验明白,确认该中的中、不该中的不中,再落到代码里,最后用单测兜底。

调试正则,功夫一大半花在"看清楚它在匹配什么"上。把 flags 配对、把贪婪和非贪婪搞清楚,再避开回溯的坑,基本就顺了。
工具链接:https://www.xingdian.net/app/regex/tester/

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询