天天看點

JS程式設計建議——46:提高正規表達式執行效率

建議46:提高正規表達式執行效率

(1)關注如何讓比對更快失敗

正規表達式處理慢往往是因為比對失敗過程慢,而不是比對成功過程慢。使用正規表達式比對一個很大字元串的一小部分,情況更為嚴重,正規表達式比對失敗的位置比比對成功的位置要多得多。一個修改使正規表達式比對更快但失敗更慢,例如,通過增加所需的回溯次數嘗試所有分支的排列組合,這通常是一個失敗的修改。

(2)正規表達式以簡單的、必需的字元開始

最理想的情況是,一個正規表達式的起始字元應當盡可能快速地測試并排除明顯不比對的位置。用于此目的好的起始字元通常是一個錨(^或$)、特定字元(如x 或u363A)、字元類(如[a-z]或速記符、單詞邊界(b))。如果可能,避免以分組或選擇字元開頭,避免頂級分支,如/one|two/,因為這樣會強迫正規表達式識别多種起始字元。

Firefox浏覽器對起始字元中使用的任何量詞都很敏感,能夠優化得更好。例如,以ss*替代s+或s{1,}。其他浏覽器大多優化掉這些差異。

(3)編寫量詞模闆,使它們後面的字元互相排斥

(4)減少分支的數量,縮小它們的範圍

當分支使用 | (豎線)時,可能要求在字元串的每一個位置上測試所有的分支選項。通常可通過使用字元類和選項元件減少對分支的需求,或者将分支在正規表達式上的位置推後(允許到達分支之前的一些比對嘗試失敗)。

字元類比分支更快,因為它們使用位向量實作(或其他快速實作)而不是回溯。當分支必不可少時,在不影響正規表達式比對的情況下,将常用分支放在最前面。分支選項從左向右依次嘗試,一個選項被比對上的機會越多,它被檢測的速度就越快。

注意:由于Chrome 和Firefox 浏覽器自動執行這些優化中的某些項目,是以較少受到手工調整的影響。

(5)使用非捕獲組

捕獲組花費時間和記憶體用于記錄後向引用,并保持它們是最新的。如果不需要一個後向引用,可通過使用非捕獲組避免這種開銷,例如,(?:…)替代(…)。當需要一個完全比對的後向引用時,有些人喜歡将正規表達式包裝在一個捕獲組中,這是不必要的,因為可以通過其他方法引用完全比對。例如,使用regex.exec()傳回數組的第一個元素,或替換字元串中的$&。用非捕獲組取代捕獲組在Firefox 浏覽器中影響很小,但在其他浏覽器上處理長字元串時影響很大。

(6)捕獲感興趣的文字,減少後處理

(7)暴露所需的字元

為幫助正規表達式引擎在如何優化查詢例程時做出明智的決策,應盡量簡單地判斷出那些必需的字元。當字元應用在子表達式或分支中時,正規表達式引擎很難判斷它們是不是必需的,有些引擎并不做此方面的努力。例如,正規表達式/^(ab|cd)/暴露它的字元串起始錨。IE 和Chrome浏覽器會注意到這一點,并阻止正規表達式嘗試查找字元串頭端之後的比對,進而使查找瞬間完成而不管字元串長度。但是,由于等價正規表達式/(^ab|^cd)/不暴露它的^錨,IE無法應用同樣的優化,最終無意義地搜尋字元串并在每一個位置上比對。

(8)使用适當的量詞

正如建議45所讨論過的那樣,“貪婪”量詞和“懶惰”量詞即使比對同樣的字元串,其查找比對過程也是不同的。在確定正确等價的前提下,使用更合适的量詞類型(基于預期的回溯次數)可以顯著提高性能,尤其在處理長字元串時。

(9)将正規表達式賦給變量,以重用它們

将正規表達式賦給變量以避免對它們重新編譯。有人使用正規表達式緩存池,以避免對給定的模闆和标記組合進行多次編譯。不要過分擔心,正規表達式編譯得很快。重要的是避免在循環體中重複編譯正規表達式。換句話說,不要這樣做:

while (/regex1/.test(str1)) {

}

替代做法如下:

var regex1 = /regex1/,

regex2 = /regex2/;

while (regex1.test(str1)) {

(10)将複雜的正規表達式拆分為簡單的片斷

盡量避免一個正規表達式做太多的工作。處理複雜的搜尋問題需要将條件邏輯拆分為兩個或多個正規表達式,這樣更容易解決問題,通常也更高效,每個正規表達式隻在最後的比對結果中執行查找。在一個模闆中完成所有工作的正規表達式很難維護,而且容易引起回溯相關的問題。

繼續閱讀