在正则表达式设计中,必须考虑匹配的回溯:若是匹配失败,就要从可能的上一个分支从新进行匹配。正则表达式
回溯设计,极大的下降了匹配的效率,让一些简单的匹配耗费大量的资源。缓存
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.
过多的回溯设计,会让解析器结果须要更大的缓存,在流数据处理上,效率影响很大。
正则表达式虽然很好用,但在处理复杂的数据结构上,依然有不少自然的缺陷,这时候,就要考虑使用另外的匹配工具:语法匹配。
下次在讲语法匹配。