在正則表達式設計中,必須考慮匹配的回溯:若是匹配失敗,就要從可能的上一個分支從新進行匹配。正則表達式
回溯設計,極大的下降了匹配的效率,讓一些簡單的匹配耗費大量的資源。緩存
pattern = / a.*?abc /
這就是典型的一個須要回溯支持的正則表達式,在每次匹配字符的時候,都要試着匹配 a. 若是匹配成功,則設置一個錨點,繼續匹配,若是在匹配 abc 過程當中失敗,那麼匹配過程(自動機)將重置到最近設置的錨點,前進一位後,繼續相同的過程。數據結構
這種匹配效率很低。函數
那麼如何設計正則表達式,消除這種回溯呢?工具
須要一個不匹配字符串單元:性能
pattern = / a (!abc)* /
彷佛,正則表達式沒有這種設計,是的,絕大多數正則表達式沒有這種匹配單元。只有字符級別的否認:設計
pattern = / a [^abc]* /
正則表達式設計的引領者,Perl 語言的先驅們,在 Perl6 的設計中提出了 Longest Matching 概念。也就是最長匹配:code
pattern = / abc | abcd /
這個匹配中,一般會匹配先命中的規則,但 Perl6 會嘗試全部的匹配,只返回那個匹配字符長度最長的部分。 這聽起來很不錯,但這須要犧牲正則表達式的性能。若是提早對規則進行排序(假設都是字符串),那麼就不須要這種方式了。排序
回溯設計,是由於許多規則有重複,因此纔不得不設計回溯引擎來讓匹配能夠中途中止,從新來過。資源
funcName = / \a+ / variable = / \a+ /
在許多語言中,函數的名稱和變量的名稱,用的是同樣的規則,但實際上,他們是不一樣的東西:函數名稱一般要調用參數,而變量則直接返回值。 pattern = / return funcname / pattern = / variable(callargs) /
這兩種狀況都是錯誤的語法,但卻沒法在匹配中識別,但在 PHP 中:
funcname = / [_\a]+ / variable = / \$[_\a]+ /
PHP 語言的解析器,在識別函數名稱和變量上,更簡單,固然匹配速度更快。
規則衝突設計的越多,在語法樹的處理上就越複雜,在 Java 中:
Name.Name
多是一個 class 的名稱,一個類型的名稱,也多是方法調用,甚至是結構的 field 調用,這須要看的人有至關的知識才能分辨。 但在 Perl 中,則不須要這些東西:
Class name: Name::Name Object call: Name->Name get field: Name->{Name}
這是語言設計上對回溯的影響,在實際的工做中,咱們須要設計一些正則來匹配規則,若是不考慮類似規則的回溯,效率會很低。
如何設計規則,儘可能避免回溯呢?
越早分辨越好
徹底不回溯的規則匹配,是設計了徹底不一樣的首字符:
group = / ( ... ) / chars = / [ ... ] / expr = / { ... } / string = / " ... " / char = / ' ... ' /
這些規則的第一個字符不一樣,因此在匹配中,不會出現回溯問題,也不用擔憂順序問題。在有回溯的設計中,不一樣的順序可能會出現不一樣的結果。
pattern = / keyword keywordname /
這個匹配永遠不會匹配到完整的 keywordname.
過多的回溯設計,會讓解析器結果須要更大的緩存,在流數據處理上,效率影響很大。
正則表達式雖然很好用,但在處理複雜的數據結構上,依然有不少自然的缺陷,這時候,就要考慮使用另外的匹配工具:語法匹配。
下次在講語法匹配。