一次性子组

一次性子组概念


之前我们介绍过贪婪模式,贪婪模式的遍历过程以及非贪婪模式的遍历过程。重复性量词是造成贪婪的原因。所谓贪婪就是尽可能多的去匹配,当下一个正则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",那断言失败,则整个正则表达式就匹配失败,.*部分不用再回溯。可以很好的提高匹配的性能。

所以说在程序中善于运用一次性子组和断言,可以很好的提升匹配的速度,从而提高程序的性能。

查看笔记

扫码一下
查看教程更方便