一次性子组
一次性子组概念
之前我们介绍过贪婪模式,贪婪模式的遍历过程以及非贪婪模式的遍历过程。重复性量词是造成贪婪的原因。所谓贪婪就是尽可能多的去匹配,当下一个正则token匹配失败的时候,引擎会因为量词的贪婪性进行回溯匹配,重复多次的去回溯。这会造成性能的损失。所谓一次性子组
就是将量词所要重复的正则作为一个子组,并且只要是量词后的token匹配失败了,那整个子组就不再进行回溯,在没有可选路径的情况下,整个正则宣告匹配失败。一次性子组
用(?>
来表示。
下面我们先来看一个贪婪的例子,正则表达式/<(.+)>/
匹配字符串 "<span>Hello World</span>Test"
。这个正则可以匹配出字符串"<span>Hello World</span>"
。它的匹配过程在贪婪和量词
一节介绍过,这里我们简单回顾一下。首先token <
匹配字符串的第一个'<'
;然后是点号.
可以匹配任意字符,再加上+
的贪婪性,所以由(.+)
匹配到的是"span>Hello World</span>Test"
。由于到了行尾,所以点号在默认情况下不能再匹配;正则中的下一个token >
无法匹配行尾,所以匹配失败,这时正则引擎知道+
是贪婪的,所以开始回溯,此时(.+)
匹配的是"span>Hello World</span>Tes"
,同样 token >
还是不能匹配字符't'
。正则引擎继续回溯,直到(.+)
匹配的是"span>Hello World</span"
,此时的token >
和字符'>'
可以匹配成功,因此整个表达式匹配成功。我们看,在这个过程中正则引擎是回溯了很多次才正确匹配成功的。那如果我们在这个例子中使用一次性子组
,/<(?>.+)>/
,<
正常匹配这没什么问题,(?>.+)
可以匹配到行尾,token >
不能匹配行尾,所以匹配失败。由于前面是一次性子组,所以正则引擎不再进行回溯,因此整个正则就匹配失败了。
代码示例
<?php
$str = "<span>Hello World</span>Test";
$pattern = "/<(?>(.+))>/";
$res = preg_match($pattern,$str,$matches);
print_r($matches);
/*** 执行结果
Array
(
)
*/
当然,上面的例子只是用来说明一次性子组的使用原理,实际情况中,我们当然是期望正则表达式要能匹配出我们想要的内容来了。
一次性子组
的特点决定了它的性能是很高的。一般情况下可以和后瞻断言
结合使用。还是上面字符串<span>Hello World</span>Test
,要匹配整个字符串,并且字符串须是以"Test"
结尾。当然我们可以使用/.*Test/
来匹配,但是上面我们说过在匹配Test
的时候,正则引擎要回溯四个字符才能匹配上Test
。假如目标字符串不是以"Test"
结尾,那就要回溯整个目标字符串。如果我们使用一次性子组
和后瞻断言
,这个问题就很好解决。/(?>.*)(?<=Test)/
,当(?>.*)
匹配了整个字符之后,后瞻断言(?<=Test)
只需要判断字符串的后四个是不是"Test"
就可以了,如果不是"Test"
,那断言失败,则整个正则表达式就匹配失败,.*
部分不用再回溯。可以很好的提高匹配的性能。
所以说在程序中善于运用一次性子组和断言,可以很好的提升匹配的速度,从而提高程序的性能。