1. 程式人生 > >正則表示式的效率與優化

正則表示式的效率與優化

以下內容整理自《PHP核心技術與最佳實踐》

一、使用字元組代替分支條件

eg. 使用[a-d]表示a~d之間的字母,而不是使用(a|b|c|d)

function regTest($pattern,$str,$cnt){
    $start=microtime(true);
    for ($i=0;$i<$cnt;$i++){
        preg_match($pattern,$str);
    }
    echo 'waste time(s): ',number_format(microtime(true)-$start, 10),'<br>';
}
$cnt
=15;//最好設大數,eg.1000 $str=''; for ($i=0;$i<$cnt;$i++){ $str.='abababcdefg'; } //方案1:分支條件 regTest("/^(a|b|c|d|e|f|g)+$/",$str,$cnt); //方案2:字元組 regTest("/^[a-g]+$/",$str,$cnt); //方案3:同方案2 regTest("/^[abcdefg]+$/",$str,$cnt);

這裡寫圖片描述
可以看出使用字元組比使用分支條件速度快很多。這是由於在匹配單個字元的時候,引擎會把[abc]這樣的字元組視為一個元素,而不是3個元素(a、b、c)。整個元素作為匹配迭代的一個單元,不需要進行三次迭代,從而提高匹配效率。

二、優化選擇最左端的匹配結果

對於傳統NFA引擎來說,因為引擎一旦找到匹配結果就會停下來,而不會去嘗試正則表示式的每一種可能(PHP中的preg函式就屬於傳統型NFA引擎)。

三、標準量詞是匹配優先的

若用量詞約束某個表示式,那麼在匹配成功前,進行的嘗試次數有下限和上限。eg.

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

       這條正則表示式匹配的$1結果應該是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+)”匹配下一個字元,同時記錄一個點,用於匹配不成功時,返回這裡再次匹配。
       儘量以組為單位進行匹配,使用固話分組就能避免無休止的匹配。

四、謹慎用點號元字元,儘可能不用星號和加號這樣的任意量詞

只要能確定範圍(eg.“\w”),就不要用點號;只要能夠預測重複次數,就不要用量詞。假設一條微博訊息的XML正文部分結構如下:

<span class="msg">...</span>

正文中無尖括號,寫法如下:

<span class="msg">[^<]{1,200}</span>

或者:

<span class="msg">.*</span>

上述第一種程式碼的思路要好於第二種,原因如下:
1、使用“[^<]”,保證了文字的範圍不會超過下一個小於號所在位置
2、明確長度範圍{1,200},依據是一條微博訊息大致的字元長度範圍是固定的,現在微博字數長度限制是140個字。
同時,能使用懶惰匹配就堅決不用貪婪匹配。

五、儘量使用字串函式處理代替

使用字串函式和正則表示式都可以處理字串,兩者相比,字串函式處理的效率更高。當然,有些情況幾乎是非正則表示式不能勝任的,或者不用正則表示式的成本太高,這些情況不得不用正則表示式。

六、合理使用括號

每使用一個普通括號(),而不是非捕獲型括號(?:),就會保留一部分記憶體等著再次訪問。

七、起始、行描點優化

能確定起止位置,使用^能提高匹配的速度。同理,使用$標記結尾,正則引擎則會從符合條件的長度處開始匹配,略過目標字串中許多可能的字元。在寫正則表示式時,應該將描點獨立出來,例如“^(?:abc|123)”比“^123|^abc”效率高,而“^(abc)”比“(^abc)”效率更高。
這個原則不適用於所有正則引擎。比如在PCRE中,二者效率相當。

八、量詞等價轉換的效率差異

例如在PHP中,使用“\d\d\d”和“\d{3}”,或者“====”和“={4}”,它們之間的效率幾乎沒有差別。但是使用其他語言可能就會有比較明顯的效能差異了。

九、對大而全的表示式進行拆分

十、使用正則以外的解決方案

eg.
1、 同五;
2、在某專案需要分析PHP程式碼,分離出對應的函式呼叫(以及原始碼對應的位置)。雖然這些正則表示式也可以實現,但無論從效率還是程式碼複雜度方面考慮,這都不是最有方法。PHP已經內建解析器的介面PHP Tokenizer。使用PHP Tokenizer能簡單、高效、準確地分析出PHP原始碼的組成;
3、在解析URL時沒必要使用正則表示式,使用parse_url函式即可;
4、在獲取HTTP頭時,也可以使用get_headers函式;
5、在進行輸入校驗時,可以使用filter_var函式,如:

filter_var($email,FILTER_VALIDATE_EMAIL);

6、如果在JavaScript裡,可以使用DOM代替一些正則匹配。