4. 量词与贪婪模式:星号、加号、问号、花括号与贪婪懒惰匹配

量词这东西,说白了就是告诉正则引擎「前面的那个字符要出现多少次」。我刚开始学正则的时候,总觉得量词就是简单的计数工具,后来踩了几个坑才发现——量词背后的贪婪与懒惰机制,才是真正让人头疼的地方。

咱们一个一个来拆解。

4.1 基础量词:* + ? {n,m}

先看最常用的四个量词:

量词 含义 等价于 举个栗子
* 出现 0 次或多次 {0,} ab*c 匹配 ac、abc、abbc
+ 出现 1 次或多次 {1,} ab+c 匹配 abc、abbc,不匹配 ac
? 出现 0 次或 1 次 {0,1} ab?c 匹配 ac、abc
{n,m} 出现 n 到 m 次 精确范围 a{2,4} 匹配 aa、aaa、aaaa

嗯,这里要注意:* 和 ? 都能匹配「零次」,但场景完全不同。* 是「有也行没有也行,多了更好」,? 是「最多一次,多了不要」。我在项目中见过有人用 .* 去匹配可选字段,结果把不该匹配的内容也吞进去了——这就是没分清「零次」和「任意次」的区别。

4.2 花括号的细节

{n,m} 有三种写法:

  • {n} —— 精确匹配 n 次。比如 \d{3} 匹配三位数字。
  • {n,} —— 至少 n 次,上不封顶。等价于 {n,} 就是 + 的精确版。
  • {n,m} —— n 到 m 次,包含两端。

我个人习惯在需要精确控制次数时用花括号,而不是用 + 或 * 加其他逻辑。比如匹配电话号码区号 0\d{2,3},一眼就能看出是 3 到 4 位数字,可读性比 0\d\d?\d? 强太多了。

小技巧: 花括号里的 m 可以省略,但逗号不能省。{3,} 是「至少 3 次」,{3} 是「恰好 3 次」。少写一个逗号,意思全变了。

4.3 贪婪匹配:默认行为

默认情况下,所有量词都是贪婪的。什么叫贪婪?就是引擎会尽可能多地匹配字符,直到匹配失败才回溯。

举个例子:

文本:<div>内容1</div><div>内容2</div>
正则:<.*>

你猜匹配结果是什么?很多人以为是 <div>,但实际匹配的是:

<div>内容1</div><div>内容2</div>

为什么会这样?因为 .* 会一直吞到最后一个 > 才停下。这就是贪婪的代价——它总想拿最多的。

我记得有一次做日志解析,想提取 [ERROR] 和 [WARN] 之间的内容,用了 \[ERROR\].*\[WARN\],结果把中间所有日志都吞了。当时排查了半天,最后发现是贪婪匹配搞的鬼。

4.4 懒惰匹配:加个问号

解决贪婪问题的方法很简单——在量词后面加个 ?,变成懒惰模式。

贪婪写法 懒惰写法 含义
* *? 尽可能少地匹配
+ +? 尽可能少地匹配
? ?? 尽可能少地匹配(优先匹配 0 次)
{n,m} {n,m}? 尽可能少地匹配

用懒惰模式重写上面的例子:

文本:<div>内容1</div><div>内容2</div>
正则:<.*?>

这次匹配的是 <div>,因为 .*? 遇到第一个 > 就停了。你想想看,这才是我们想要的效果。

核心区别: 贪婪匹配从右向左回溯,懒惰匹配从左向右推进。贪婪追求「最多」,懒惰追求「最少」。大部分场景下,懒惰匹配更符合人的直觉。

4.5 实战对比:贪婪 vs 懒惰

咱们用同一个文本,看看不同写法的效果:

文本:a123b456c
正则 模式 匹配结果
a.*b 贪婪 a123b456b(从 a 到最后一个 b)
a.*?b 懒惰 a123b(从 a 到第一个 b)
a.+b 贪婪 a123b456b
a.+?b 懒惰 a123b

看到没?同样的量词,加个 ? 结果天差地别。我曾经在写爬虫解析 HTML 时,用贪婪匹配去拿标签内容,结果把整个页面都吞了——从那以后,只要涉及多段匹配,我第一反应就是加 ?。

4.6 知识体系总览

下面这张图把量词和贪婪/懒惰的关系梳理清楚了:

量词与贪婪/懒惰匹配知识体系 量词(Quantifiers) * 零次或多次 + 一次或多次 ? 零次或一次 {n,m} 精确范围 贪婪模式(默认) 懒惰模式(加 ?) 尽可能多匹配 从右向左回溯 容易过度匹配 尽可能少匹配 从左向右推进 适合提取单段内容 选择原则:需要精确范围用 {n,m},需要最小匹配用懒惰

4.7 避坑指南

我曾经踩过的坑:
  • 坑1: 用 .* 匹配跨行内容。默认 . 不匹配换行符,要用 [\s\S]* 或加 s 修饰符。
  • 坑2: 懒惰匹配不是万能的。如果文本结构复杂,懒惰匹配也可能匹配到错误的位置——它只是「尽可能少」,不是「最合理」。
  • 坑3: ?? 优先匹配 0 次,所以 a??b 在 ab 中匹配的是 b 而不是 ab。这个很容易搞混。

4.8 总结一下

量词是正则表达式的「次数控制器」,而贪婪/懒惰是它的「匹配策略」。记住三条原则:

  1. 默认是贪婪,加 ? 变懒惰。
  2. 提取单段内容用懒惰,匹配整体结构用贪婪。
  3. 花括号 {n,m} 是最精确的量词,能不用 * 和 + 的地方尽量用花括号。