转载请注明出处:https://www.jianshu.com/p/0e0dc45c2548
本文出自 容华谢后的博客
往期回顾:
《一起学习正则表达式(一)那些让人头晕的元字符》
《一起学习正则表达式(二)量词与贪婪》
《一起学习正则表达式(三)分组与引用》
《一起学习正则表达式(四)常见的4种匹配模式》
《一起学习正则表达式(五)断言匹配》
《一起学习正则表达式(六)正则匹配原理》
《一起学习正则表达式(七)回溯陷阱》
0.写在前面
在上一篇文章中,我们学习了正则的一些基础元字符,相信大家都已经忘却的差不多了,可以点击上面的链接再温习下。
今天我们一起来学习下正则中量词的三种匹配模式,贪婪模式、非贪婪模式、独占模式,这些模式会改变正则中量词的匹配行为,是每次贪婪的匹配到更多呢,还是不贪婪见好就收呢,如果不了解这些,我们写出的正则很可能是错误的,甚至会引发严重的线上性能问题。
1.量词
本篇文章所讲的内容和量词关系比较密切,先回顾下:
我们还可以用 {m,n} 的方式来表示 * + ? 这3种元字符:
元字符 | 同义表示方法 | 示例 |
---|---|---|
* | {0,} | ab* 可以匹配 a 或者 abb |
+ | {1,} | ab+ 可以匹配 ab 或者 abb 但不能匹配 a |
? | {0,1} | ab? 可以匹配 a 或者 ab 但不能匹配 abb |
2.贪婪模式前传
在正则中,表示次数的量词默认是贪婪的,在贪婪模式下,会尽可能最大长度的去匹配目标字符串,我们用正则 a+ 和 a* 来匹配字符串 aaabb 测试一下。
2.1 使用 a+ 进行匹配
可以看到只匹配到了1个结果 aaa
对应的 Python 代码如下:
import re
print(re.findall(r'a+', 'aaabb'))
输出:['aaa']
2.2 使用 a* 进行匹配
可以看到匹配到了4个结果,其中还有3个是空字符串
对应的 Python 代码如下:
import re
print(re.findall(r'a*', 'aaabb'))
输出:['aaa', '', '', '']
为什么会匹配到空字符串呢?因为星号(*)代表匹配0到多次,匹配0次就是空字符串,那前面还有个 aaa 呢,为什么 aaa 之间的空字符串没有被匹配到?
这就引入到了我们今天要讲的,贪婪模式与非贪婪模式,从字面上很好理解,贪婪模式就是尽可能多的匹配,非贪婪模式就是尽可能少的匹配。
3.贪婪模式
一起来分析下上面正则 a* 的匹配过程:
字符串 | a | a | a | b | b | 空字符串 |
---|---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
匹配 | 开始 | 结束 | 说明 | 匹配内容 |
---|---|---|---|---|
第一次 | 0 | 3 | 到第一个字母b发现不匹配,输出aaa | aaa |
第二次 | 3 | 3 | 匹配剩下的bb,发现匹配不上,输出空字符串 | 空字符串 |
第三次 | 4 | 4 | 匹配剩下的b,发现匹配不上,输出空字符串 | 空字符串 |
第四次 | 5 | 5 | 匹配剩下的空字符串,输出空字符串 | 空字符串 |
a* 在匹配字符串 aaabb 时,会尽可能多的把前面的 a 都匹配上,直到第一个字母 b 不满足要求为止,匹配上3个 a,后面每次匹配的都是空字符串。
看到这里,相信你已经对贪婪模式有了更深的印象,贪婪模式的特点就是尽可能进行最大长度匹配,就是有多少要多少,下面我们在一起来看下与它完全相反的匹配模式。
4.非贪婪模式
上面讲完了贪婪模式,贪婪模式是尽可能最大长度匹配,非贪婪模式就是尽可能最小长度匹配,在量词的后面加一个问号(?),就成了非贪婪模式,比如 a*?
对应的 Python 代码如下:
import re
// 贪婪匹配
print(re.findall(r'a*', 'aaabb'))
输出:['aaa', '', '', '']
// 非贪婪匹配
print(re.findall(r'a*?', 'aaabb'))
输出:['', 'a', '', 'a', '', 'a', '', '', '']
学完了贪婪模式与非贪婪模式,你可能会问,我什么情况下会用到呢,下面举个栗子感受下:
需求是查找一段字符串中,所有双引号括起来的内容,上面使用贪婪匹配与非贪婪匹配的对比,差别很明显对吧。
5.独占模式
不管是贪婪模式,还是非贪婪模式,匹配过程中都需要发生回溯才能完成想要的功能,但是在有一些场景,我们不需要回溯,匹配不上直接返回失败就可以了,因此正则匹配中还有另外一种模式,独占模式,它和贪婪模式很像,但匹配过程中不会发生回溯,在一些使用场景中性能会更好。
先来讲讲什么是回溯,再举个栗子,有一个正则表达式和目标字符串,我们分别看下在三种匹配模式下都发生了什么:
5.1 贪婪匹配过程
正则表达式:ab{1,3}c
目标字符串:abbc
在匹配时,b{1,3} 会尽可能长的去匹配目标字符串,匹配完 abb 之后,因为要尽可能长的匹配(3个 b),目标字符串中的c就会匹配不上,这个时候会发生向前回溯,吐出当前字符 c,用正则中的 c 去匹配,匹配成功。
import regex
print(regex.findall(r'ab{1,3}c', 'abbc'))
输出:['abbc']
5.2 非贪婪匹配过程
正则表达式:ab{1,3}?c
目标字符串:abbc
在匹配时,b{1,3} 会尽可能短的去匹配目标字符串,匹配完 ab 之后,会直接用正则 c 去匹配目标字符串剩下的 b,匹配不上,发生向前回溯,重新用正则 b{1,3} 匹配 目标字符串剩下的 b,然后正则 c 匹配 目标字符串剩下的 c,匹配成功。
import regex
print(regex.findall(r'ab{1,3}?c', 'abbc'))
输出:['abbc']
5.3 独占匹配过程
在量词后面加上 + 就是独占模式。
正则表达式:ab{1,2}+bc
目标字符串:abbc
在匹配时,b{1,2} 会尽可能长的去匹配目标字符串,匹配完 abb 之后,会用正则 b 匹配目标字符串剩下的 c,匹配不上,不回溯,匹配失败。
import regex
print(regex.findall(r'ab{1,2}+bc', 'abbc'))
输出:[]
6.写在最后
最后在总结下上面讲到的内容:
到这里,正则表达式的量词与贪婪就讲完了,如果有问题可以给我留言评论,谢谢。
正则表达式在线校验工具:https://regex101.com/