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? 强太多了。
{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 知识体系总览
下面这张图把量词和贪婪/懒惰的关系梳理清楚了:
4.7 避坑指南
- 坑1: 用
.*匹配跨行内容。默认.不匹配换行符,要用[\s\S]*或加s修饰符。 - 坑2: 懒惰匹配不是万能的。如果文本结构复杂,懒惰匹配也可能匹配到错误的位置——它只是「尽可能少」,不是「最合理」。
- 坑3:
??优先匹配 0 次,所以a??b在ab中匹配的是b而不是ab。这个很容易搞混。
4.8 总结一下
量词是正则表达式的「次数控制器」,而贪婪/懒惰是它的「匹配策略」。记住三条原则:
- 默认是贪婪,加
?变懒惰。 - 提取单段内容用懒惰,匹配整体结构用贪婪。
- 花括号
{n,m}是最精确的量词,能不用*和+的地方尽量用花括号。