
正则贪婪匹配vs非贪婪匹配:为什么你的规则抓多了
发布于 2026年7月24日
你写了<b>.*</b>想抓粗体标签里的内容,拿<b>Hello</b>测试完全没问题。结果放到真实HTML上跑,同一行里有两个粗体标签——<b>Hello</b> and <b>World</b>——结果没得到两个匹配,而是得到一个横跨两个标签的巨大匹配。这不是正则引擎的bug,而是*、+、{n,m}的默认行为,搞清楚原理之后只要加一个字符就能修好。
为什么.*抓的比你想象的多
正则里的量词默认是贪婪的——.*不是“匹配一些字符”的意思,而是“尽可能匹配最多的字符,只有在后面的规则实在匹配不上时才往回退一点”。
一步步拆解<b>.*</b>匹配<b>Hello</b> and <b>World</b>的过程:
<b>匹配到第一个<b>。.*一上来就把剩下的整个字符串全吞掉——一直到结尾,包括第二个<b>World</b>。- 引擎接下来需要找到
</b>才能完成匹配,于是开始从.*的末尾往回一个字符一个字符地退。 - (从末尾往回扫描)第一个能对上
</b>的位置,是字符串里最后一个</b>——不是逻辑上“应该”停的第一个位置。
所以最终匹配到的是整个<b>Hello</b> and <b>World</b>,因为贪婪匹配总是先尝试最长的可能字符串,只在必须的情况下才缩小到刚好够让后面的规则成立。
修法:加个?让量词变懒
在量词后面立刻加一个?,就能把它从贪婪翻转成懒惰(也叫“非贪婪”):*?、+?、??、{n,m}?。
懒惰量词做的是反过来的事:一上来先匹配尽可能少的字符,只有在后面的规则还匹配不上时才继续扩展。
用<b>.*?</b>匹配同一个字符串:
<b>匹配到第一个<b>。.*?一上来匹配零个字符。- 引擎检查:这个位置能匹配
</b>吗?不能(现在停在“Hello…“前面,还没到</b>)——于是.*?往后扩展一个字符,再检查一次。 - 这个过程一个字符一个字符重复,直到
.*?刚好吞掉了Hello,这时</b>立刻就能匹配上了。
结果:<b>Hello</b>和<b>World</b>分别作为两个独立的匹配返回——这几乎总是你在解析标签类或分隔符类结构时真正想要的结果。
什么时候真的该用贪婪(不是说贪婪就是“错误的默认值”)
贪婪不是这门语言设计上的失误——它对应的是另一种同样常见的场景:**抓的是某个东西最外层的边界,而不是里面最小的那个单元。**如果你要从一段类JSON文本里抓“从第一个{到最后一个}之间的所有内容”(比如不管嵌套多深,抓整个对象),这时候贪婪才是对的,用懒惰反而会在第一个内层}就提前停下,得到一段被截断、不完整的片段。
经验法则:懒惰用于抓多个重复的小分隔片段(标签、引号字符串、列表项);贪婪用于“抓整个外层跨度”。
快速参考
| 模式 | 行为 | 什么时候用 |
|---|---|---|
.*、.+、{n,m} |
贪婪——匹配尽可能长 | 想抓最外层的跨度,或者字符串里只有一处匹配 |
.*?、.+?、{n,m}? |
懒惰——匹配尽可能短 | 有多个相似的分隔片段,想把每一个分别抓出来 |
如果你不确定某个规则在真实数据(而不是测试用的短字符串)上到底是怎么匹配的,直接拿真实的多匹配文本放进正则测试工具里,靠实时高亮一眼就能看出来——贪婪会看到一大块被高亮,懒惰会看到好几块分开的高亮,通常比一步步在脑子里推理更快。