【学懂正则2】量词匹配模式:的贪婪、非贪婪和独占

总览

【学懂正则2】量词匹配模式:的贪婪、非贪婪和独占
量词的作用在上一篇已经讲过了,接下来就通过几个实例来了解贪婪模式、非贪婪模式、独占模式之间的差异

贪婪模式与非贪婪模式

贪婪模式

使用 a* 来匹配 aaabb会得到如下结果:
【学懂正则2】量词匹配模式:的贪婪、非贪婪和独占
总共有四次匹配符合,分别是:'aaa', '', '', '' 后三次都是空字符串,这也是 * 这一量词的特点,它会匹配空字符串,而正则认为字符串以空字符结尾。

如果 a* 去匹配 ‘aaa’,得到结果也会是两个,分别是'aaa', ‘’

非贪婪模式

在量词后面加上?就是非贪婪匹配了,使用 a*? 来匹配 aaabb 会得到如下结果:
【学懂正则2】量词匹配模式:的贪婪、非贪婪和独占
分别有9个匹配,分别是:'','a','','a','','a','','','' ,每个最小匹配的a前面还有一个空字符串,前面末尾还要再匹配一次空字符串已经让人觉得费解了,现在跑出来说,每个a前面还要匹配一个空字符串,翻译翻译,什么叫 * 量词加上非贪婪模式

对于使用 a*? 来匹配字符 'a',会有三种结果,分别是: '','a','',正则匹配进行的过程是这样的:
【学懂正则2】量词匹配模式:的贪婪、非贪婪和独占
可以看到,在0-0和1-1这两个端点位置进行了匹配,而每一个端点,正则都认为它是空字符串,1个字符那么就有两个断点,a的左侧是0,a的右侧是1,[0,1]区间代表了字符a,0,1这两个端点是空字符,再来看一下对aaabb的匹配结果就能够理解了:
【学懂正则2】量词匹配模式:的贪婪、非贪婪和独占
0-5的六个端点全部做了一次空字符的匹配,所以一定要注意单字符使用 *? 时候的坑点。

回溯

回溯是正则在匹配时的一个重要概念,这里的通过一个例子简单的理解一下

待匹配字符串:xyyyz

使用贪婪匹配:xy{1,3}yz

非贪婪匹配:xy{1,3}?yz

都可以匹配成功,但二者的匹配过程是不一样的:

对于贪婪模式,它会先匹配3个y,然后遇到字符串字符是z,但是希望匹配的是y,它就会吐出一个已经匹配的y来尝试进行匹配,如果成功则继续进行,如果失败则继续吐出,直到成功或者无法满足返回失败。

对于非贪婪模式,它会只匹配一个y,之后遇到的字符串字符是y,想要匹配的也是y,继续匹配,再往前,遇到的字符串字符是y,但是想要匹配的z,不满足,则回到非贪婪匹配那里,尝试多匹配一个y,多匹配是能够满足要求的,则多匹配一个,然后继续前进,遇到的字符串字符是y,想要匹配的也是y,之后是z…

在这里我们可以知道回溯贪婪和非贪婪模式下是必须的,正则在具体实施过程中会对这个过程有优化,具体这里就不阐释了。

但是这里也还是可以再讲一点的,考虑待匹配字符串 xyyz,正则为 xy{1,3}z, 那么当原字符串指针到z的时候我吃进去,发现不是y,我再吐出来,这个过程是回溯吗?

其实是这个过程并不正确,要明确的是,z并不会被吃进去,在编译原理中的语义分析阶段就是大量的正则的过程,在这个环节匹配是模拟两个栈,一个是原字符串,一个是待匹配的正则式子,二者的栈顶是:原字符串是z,正则是y{0,2}(大概意思就是可以不匹配,最多再匹配两个),此时将正则栈pop了,而不是将z吃进去了

独占模式

回溯是耗费性能的,如果以匹配了一个很长的字符串,最后发现失败,那么要回溯,如果还要多次回溯,那么可想而知是占用资源的。

独占模式就一种不回溯的贪婪匹配,在量词后面添加 + 就是独占模式,但是不同语言对独占模式的支持不同,有的语言并不支持独占模式。

考虑原字符串:xyyz
正则表达式:xy{1,2}+yz

结果就会是: No Match
【学懂正则2】量词匹配模式:的贪婪、非贪婪和独占
为什么呢,独占模式的匹配过程是这样的:

按照最长匹配匹配两个y,原字符串到z,正则是y,不符合,直接fail,它不会进行任何的回溯。

其它

关于正则回溯造成的性能问题感兴趣的可以阅读:
一个正则表达式引发的血案,让线上CPU100%异常!