首页 > 编程笔记 > PHP笔记

PHP正则表达式的效率与优化

通过前面的学习我们对正则表达式有了一定的了解,可以尝试自行定义简单的正则表达式了。在定义正则表达式时,尽管需求相同,可能每个人定义的正则表达式或多或少的都会存在一些的不同。虽然都能实现同样的效果,但是它们的执行效率有快有慢,那么该如何定义一个优秀的正则表达式呢?本节来为大家介绍一下。

1、使用字符组代替分支条件

想要匹配“a~g”之间的字母时,应该使用 [a-g] 来表示,而不是使用 (a|b|c|d|e|f|g) 表示,下面通过示例代码来说明以下二者执行效率之间的差异:
<?php
    $cnt=1000;
    $testStr="";
    for($i=0;$i<1000;$i++){
        $testStr.="abababcdefg";
    }
    //第一种方案
    $start=microtime(TRUE);
    for($i=0;$i<$cnt;$i++){
        preg_match('#^(a|b|c|d|e|f|g)+$#',$testStr);
    }
    echo '第一种方案执行时间:'.(microtime(TRUE)-$start).'<br>';
    //第二种方案
    $start=microtime(TRUE);
    for($i=0;$i<$cnt;$i++){
        preg_match('#^[a-g]+$#',$testStr);
    }
    echo '第二种方案执行时间:'.(microtime(TRUE)-$start).'<br>';
    //第三种方案,与第二种方案本质上是相同的
    $start=microtime(TRUE);
    for($i=0;$i<$cnt;$i++){
        preg_match('#^[abcdefg]+$#',$testStr);
    }
    echo '第三种方案执行时间:'.(microtime(TRUE)-$start).'<br>';
?>
运行结果如下所示:

第一种方案执行时间:0.017441034317017
第二种方案执行时间:0.0054929256439209
第三种方案执行时间:0.0054469108581543

通过运行结果可以看出,[a-g] [abcdefg] 这两种正则表达式的执行效率是差不多的,它们都比使用(a|b|c|d|e|f|g) 这样的分支结构速度要快。这是因为在匹配单个字符的时候,引擎会把 [abc] 这样的字符组视为 1 个元素,而不是 3 个元素。整个元素作为匹配迭代的一个单元,不需要进行三次迭代,从而提高匹配效率。

2、将容易匹配的结果左移

当需要使用类似 (a|b|c) 这样的分支条件时,尽量将容易匹配的条件放在最左边。因为对于传统型 NFA 引擎来说(PHP 中的 preg 系列函数就属于传统型 NFA 引擎),会从左到右匹配每个分支条件,当匹配到满足的分支条件时,就会停止不再考虑其它的条件了。

3、标准量词是匹配优先

若用量词约束某个表达式,那么在匹配成功前,进行的尝试次数是有下限和上限的。例如,正则表达式为:

preg_match('/\w*(\d+)/','copy2003y',$match);

正则表达式中 (\d+) 的匹配结果是多少呢?大家可能认为是 2003,其实并不是,它的结果应该 3,下面就来分析以下:

当使用正则表达式\w*(\d+)来匹配字符串“copy2003y”时,会先用\w*匹配字符串“copy2003y”。而\w*会匹配字符串“copy2003y”中的所有字符,然后再交给\d+来匹配剩下的字符串,而这时已经没有剩下的了。那么,\w*规则会不情愿地吐出一个字符,给\d+匹配。

同时,在吐出字符之前,记录一个点,这个点就是用于回溯的点。然后\d+匹配“y”,发现不能匹配成功,此时会要求\w*再吐出一个字符;\w*先记录一个回溯的点,再吐出一个字符。这时,\w*匹配结果只有“copy200”,已经吐出“3y”。\d+再去匹配“3”,发现匹配成功,这时会通知引擎,并且直接显示出来。所以,(\d+)的结果是 3,而不是 2003。

如果改为非贪婪模式呢?\w*?(\d+)匹配的结果就应该是 2003。由于\w*?是非贪婪的,正则引擎会用表达式\w*?每次仅匹配一个字符串,然后再将控制权交给后面的(\d+)匹配下一个字符,同时记录一个点,用于匹配不成功时,返回这里再次匹配。

4、谨慎使用点号元字符,尽可能不用星号和加号这样的任意量词

只要能够确定要匹配的范围,就不要使用点号;只要能够确定重复次数,就不要使用星号或加号。

5、优先使用函数

当我们要处理某个字符串时,虽然使用字符串处理函数和正则表达式都可以实现,但两者相比的话,还是字符串处理函数的效率更高。所以在处理字符串的时候可以优先考虑使用字符串处理函数来实现,当字符串处理函数无法实现,或者实现成本过高时,再尝试使用正则表达式。

6、起始、行描点优化

在起始的位置,使用 ^ 能提高匹配的速度。同理,在结尾处使用 $ 标记,正则引擎会从符合长度条件的地方开始匹配,略过目标字符串中的一些字符。在写正则表达式时,应该将描点独立出来,例如“^(?:abc|123)”比“^123|abc”效率要高,而“^(abc)”比“(^abc)”效率更高。

注意:这个原则不适用于所有正则引擎。比如在 PCRE 中,二者效率相当。

7、量词等价转换的效率差异

在 PHP 中,使用\d\d\d\d{3}或者====={4},他们之间的效率几乎没有差别。但是换用其他语言可能就会有比较明显性能差异了。

所有教程

优秀文章