回溯設計是對錯誤設計的一種妥協

在正則表達式設計中,必須考慮匹配的回溯:若是匹配失敗,就要從可能的上一個分支從新進行匹配。正則表達式

回溯設計,極大的下降了匹配的效率,讓一些簡單的匹配耗費大量的資源。緩存

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.

過多的回溯設計,會讓解析器結果須要更大的緩存,在流數據處理上,效率影響很大。

正則表達式雖然很好用,但在處理複雜的數據結構上,依然有不少自然的缺陷,這時候,就要考慮使用另外的匹配工具:語法匹配。

下次在講語法匹配。

相關文章
相關標籤/搜索