原子分组

原子分组


原子组是一个组,当正则表达式引擎退出该组时,它会自动丢弃该组内所有标记记住的所有回溯位置。原子组是不可捕获的。语法为(?>group)环视组也是原子的。大多数现代的正则表达式都支持原子分组,包括JGsoft,Java,PCRE,.NET,Perl,Boost和Ruby。其中大多数还支持所有格修饰符,这在本质上是原子分组的简写符号。

下面我们使用一个示例大致上了解一下原子组的行为。正则表达式a(bc|b)c(这是一个捕获组)匹配abcc和abc 。正则表达式a(?>bc|b)c(这是原子组)匹配abcc但不匹配abc 。

当应用于字符串abc ,两个正则表达式都匹配token a和字符a,token bc和字符串bc,然后c将在字符串的末尾不能匹配。在这里,他们的方式各不相同。具有捕获组的正则表达式已经记住了选择项的回溯位置。该次匹配将失效,然后token b则匹配b; token c匹配字符c。找到匹配项。Perfect!                

但是,具有原子组的正则表达式在bc匹配后从原子组处退出。此时,该组内token的所有回溯位置都将被丢弃。在此示例中,选择选项尝试在字符串的第二个位置尝试匹配b 。结果,当c失败时,正则表达式引擎没有其他选择可以尝试。

当然,上面的例子不是很有用。但是它确实非常清楚地说明了原子分组如何消除某些匹配。更重要的是,它消除了某些匹配尝试。

使用原子分组进行正则表达式优化


考虑正则表达式\b(integer|insert|in)\b和目标字符integers。显然,由于边界字符的存在,它们不匹配。正则表达式引擎将花费很多精力来解决这个问题。

\b在字符串的开头匹配,并且integer匹配integer 。正则表达式引擎注意到该组中还有两个替代方案,并以\b继续。这不能匹配r和s 。因此,引擎回溯以尝试组内的第二个选择项。第二种选择匹配in,但随后不匹配s。因此,引擎再次回溯到第三种选择。正则in匹配字符串in。\b这次在n和t之间失败。正则表达式引擎不再记住回溯位置,因此它宣告失败。

要找出integers不在我们的单词列表中,正则引擎做了很多工作。我们可以告诉正则表达式引擎,如果它在匹配integer之后不能匹配\b那么就不要再进行其他选择项的尝试,那么它就不必理会其他任何单词。这样能在很大程度上优化正则匹配的性能。我们在目标字符串中遇到的单词是一个较长的单词,并且不在我们的列表中。

我们可以通过将捕获组变成一个原子组来做到这一点:\b(?>integer|insert|in)\b。现在,当integer匹配时,引擎从原子组中退出,并丢弃其为选择项存储的回溯位置。当\b失败时,引擎立即放弃。当扫描大型文件中的一长串关键字时,这种方式对性能的提升可能非常可观。当我们的选择项包含导致灾难性回溯的重复token(更不用说重复的组)时,这种优化至关重要。

不要急着使所有组原子化。正如我们在上面的第一个示例中看到的那样,原子分组也会排除有效匹配项。将\b(?>integer|insert|in)\b\b(?>in|integer|insert)\b应用于字符串insert时。前者的正则表达式匹配,而后者则失败。如果不是原子组,则两个正则表达式都能匹配。请务必记住,选择项尝试从左到右进行选择。如果第二个正则表达式匹配到in,则由于原子组的原因,它将不会尝试其他两个替代方法。  

查看笔记

扫码一下
查看教程更方便