打破后瞻断言的限制

保持文本正好匹配,使之不与正则表达式总体匹配


Lookbehind通常用于匹配某些文本,这些文本以其他文本开头,而在整个正则表达式匹配中不包括那些文本。(?<=ħ)d仅匹配字符串adhd 中的第二个字符d。尽管许多regex都支持后瞻断言,但大多数regex样式只允许在前瞻断言内部使用正则表达式语法的子集。Perl和Boost要求后瞻断言内部的长度是固定的。PCRE和Ruby允许使用不同长度的选择项,但除固定长度{n}之外,仍不允许使用量词

为了克服后瞻断言的限制,Perl 5.10,PCRE 7.2,Ruby 2.0和Boost 1.42引入了一项新功能,该功能可以代替后瞻断言的使用。\K会将文本匹配到当前正则表达式总体之外。h\Kd仅匹配字符串adhd中的第二个字符d。

该JGsoft一直支持无限制的后瞻断言,这要比\K更加的灵活。不过,如果我们喜欢这种工作方式,JGsoft V2会增加对\K的支持。

瞧一瞧正则表达式引擎内部


让我们看看h\Kd是如何工作的。引擎从字符串的开始位置尝试匹配。^h不能匹配a。没有其他尝试。字符串开头的匹配尝试失败。

引擎在字符串中前进一个字符,然后再次尝试匹配。h不能匹配d 。   

引擎在字符串中再次前进,正则标记 h与字符h匹配。引擎通过正则表达式前进。现在,正则表达式已到达\K,并且引擎在字符串的h和第二个d之间的位置。\K除了告诉我们如果该匹配尝试成功结束之外,什么也没有做,正则表达式引擎应该假装该匹配尝试从h和d之间的当前位置开始,而不是从它真正开始的第一个d和h之间开始。

引擎通过正则表达式前进。d与字符串中的第二个d匹配从而找到一个整体匹配项。由于\K保存了位置,因此将字符串中的第二个d作为整体匹配项返回。

\K仅影响成功匹配后返回的位置。在匹配过程中,它不会移动匹配尝试的开始。正则表达式hhh\Kd可以匹配字符串hhhhd 中的字符d。此正则表达式首先在字符串的开头匹配hhh 。然后\K记下字符串中hhh和hd之间的位置。则d与字符串中的第四个h不匹配。字符串开头的匹配尝试失败。

现在,引擎必须在开始下一个匹配尝试之前在字符串中前进一个字符。它从匹配尝试的实际开始位置(字符串的开始)开始。\K存储的位置不会更改此位置。因此,第二次匹配尝试将从字符串中第一个h之后的位置开始。从此处开始,hhh匹配hhh ,\K记下位置,d匹配d 。现在,考虑了\K记住的位置,并且将d作为整体匹配项返回。

\K可以在任何地方使用


除了在后瞻断言的内部,我们几乎可以在任何正则表达式中的任何位置使用\K。我们可以在组中使用它,即使他们有量词也是如此。可以根据需要在正则表达式中包含\K实例的数量。(ab\Kc|d\Ke)f在以ab开头时与cf匹配。当d开头时,它也匹配ef 。

\K不会影响捕获组。当(ab\Kc|d\Ke)f与cf匹配时,捕获组将捕获abc ,就好像\K不在那儿一样。当正则表达式匹配ef时,捕获组将存储de 。

\K的局限性


因为\K不会影响正则表达式引擎执行匹配过程的方式,所以它提供的灵活性比Perl,PCRE和Ruby中的回溯性要高。我们可以在\K的左侧放置任何内容,但仅限于在后瞻断言内部放置的内容。

但是,这种灵活性确实要付出代价。lookbehind确实会在字符串中向后移动。这允许在匹配尝试开始之前进行后向检查以寻找匹配项。当在上一次匹配结束时开始进行匹配尝试时,lookbehind可以匹配属于上一次匹配的文本。而\K无法执行此操作,这恰恰是因为它不影响正则表达式引擎执行匹配过程的方式。

如果使用(?<=a)a迭代字符串aaaa中所有匹配项,那么我们将会获得三个匹配项:字符串中的第二个,第三个和第四个a 。第一次匹配尝试始于字符串的开头,但由于回溯失败而失败。第二次匹配尝试在第一个和第二个a之间开始,后瞻断言成功,第二个a被匹配。第三次匹配尝试从刚刚匹配的第二个a开始。在这里,后瞻断言也成功了。前面的a是前面匹配的一部分,但是这并没有什么关系。因此,第三次匹配尝试将匹配第三个a 。同样,在第四次匹配尝试第四个a。第五次匹配尝试从字符串的末尾开始。后瞻断言仍然成功,但剩下的字符串中没有和正则标记a相匹配的,因为已经到了字符串的末尾了。匹配尝试失败。引擎已到达字符串的末尾,并且迭代停止。五次匹配尝试找到了三个匹配项。

当我们使用a\Ka遍历字符串aaaa时,情况会有所不同。我们只会得到两个匹配项:第二个和第四个a 。首次匹配尝试始于字符串的开头。正则表达式的第一个a匹配的字符串中的第一个字符a。\K标记位置。第二个正则标记a匹配字符串中的第二a,它被作为第一个匹配成功项而返回。第二次匹配尝试在刚刚匹配的第二个a之后开始。在正则表达式中的第一个标记a匹配字符串中的第三个字符a。\K标记位置。第二正则标记a匹配字符串中的第四个字符a。 第三次匹配尝试从字符串的末尾开始。字符标记a失败。引擎已到达字符串的末尾,并且迭代停止。尝试三次匹配找到了两个匹配项。

基本上,当\K之前的正则表达式部分与\K之后的正则表达式部分匹配相同的文本时,就会遇到此问题。如果这些部分不能匹配相同的文本,则使用\K的正则表达式将找到与使用lookbehind重写的相同正则表达式相同的匹配项。在这种情况下,我们应该使用\K而不是使用lookbehind,因为这样可以在Perl,PCRE和Ruby中提供更好的性能。

另一个局限性是,尽管后瞻断言有肯定和否定两种形式,但是\K不能提供否定任何东西的方法。(?<!a)b完全匹配字符串b ,因为它是不带“a”的“b”。[^a]\Kb与字符串b不匹配。尝试匹配时,[^a]匹配b 。现在,正则表达式已到达字符串的末尾。\K标记此位置。但是,现在没有什么可以让b匹配了。匹配尝试失败。[^a]\Kb(?<=[^a])b 是相同的,这两个和(?<!a)b 是不同的。  

查看笔记

扫码一下
查看教程更方便