16-零宽预测回顾断言

16.1 预测、回顾零宽断言

预测零宽断言、回顾零宽断言和位置字符以及词语边界类似,他们都是零宽的。但是预测、回顾零宽断言实际上会匹配字符,他们仅仅返回匹配是否成功,而忽略匹配到的字符。这就是为什么他们被称为“断言”。预测、回顾断言在匹配的过程中不消耗任何字符,它们仅仅断言匹配是可能的还是不可能。预测、回顾断言所提供的功能无法使用其他token替代,或者说如果使用其他方法表达式会变得很复杂。

16.2 正向预测以及负向预测

如果你想匹配一个字符串,并且它的后面不出现另一个字符串,那么你必须使用负向预测断言。字符集(例如q[^u])不能起到相同的结果,因为字符集不是零宽的。在这里我们可以使用负向预测:q(?!u)。我们可以使用一对圆括号来创建负向预测,其中(的后面紧跟着一个问号?和一个感叹号!,而字符 u 是一个普通的字面量。

正向预测和负向预测类似。q(?=u)可以匹配一个后面紧跟着字符 u 的字符 q,并且 u 不会进入匹配结果。正向预测的语法是(?=xxx),其中 xxx 是需要匹配的表达式。

你可以在预测断言中使用任何合法的正则表达式(回顾断言中并非如此)。你可以在预测断言中使用捕获组和回溯引用,即使回溯引用在断言之外回溯引用也可以正常使用。预测断言本身不是一个捕获组,所以它不会影响回溯引用中的数字索引号。如果你想保存断言中的匹配结果,你可以在断言中的表达式之外添加一个捕获组,例如(?=(regex))。其它的方法都不能达到这个目的,因为预测断言在匹配完成之后立即丢弃了匹配结果,在此之后不能保存匹配的结果。

16.3 引擎内部工作原理

首先,我们看一下表达式q(?!u)是如何匹配 Iraq 的。表达式的第一个token是q,引擎会一直遍历字符串直到第四个字符 q 。此时引擎已经匹配到字符串中位于字符 q 后面的void,而正则表达式中下一个token是一个预测断言。引擎开始匹配断言中的表达式u,他要匹配的字符是void,匹配没有成功。此时引擎知道预测断言中的表达式匹配失败了,因为这个预测断言是一个负预测断言,所以这个预测断言本身在当前位置是匹配成功的。此时整个表达式匹配成功了,匹配的结果是字符 q。

我们再来看一看这个表达式是如何匹配 quit 的。第一个tokenq匹配字符 q 。下一个token是负预测中的表达式u,下一个字符是 u ,所以匹配成功了。引擎继续匹配下一个字符 i ,但是上一个token处于预测断言中,并且其中的正则匹配是成功的,所以引擎会丢弃匹配结果并且会退到字符 u 的位置。因为这个预测断言是负预测断言,所以对于整个断言来说匹配失败了。此时表达式中已经没有其他排列了,所以引擎会回溯到开头重新匹配。由于剩下的字符串中没有字符可以和tokenq匹配,所以整个表达式匹配失败了。

我们再多看一个例子以确保我们正的弄懂了预测断言的原理。我们使用表达式q(?=u)i匹配字符串 quit。这个表达式中的预测断言是一个正预测,并且断言的后面是一个tokeni。和之前一样,q 和 q 匹配,u 和 u匹配。同样预测断言中的匹配结果会被丢弃,并且引擎从字符 i 退回到字符 u。这个正预测断言的匹配是成功的,所以引擎继续匹配下一个tokeni。因为 i 无法和字符 u 匹配,所以tokeni匹配失败了。此时引擎回退到正则表达式的第一个token重新进行匹配,并且字符串中剩下的部分都不能和tokenq匹配,所以整个表达式匹配失败了。

16.4 正回顾、负回顾

回顾断言和预测断言的效果类似,但是它的判断方向与预测断言相反。如果引擎遇到回顾断言,引擎会暂时回退它所在字符串中的位置,并且使用断言中的表达式来匹配那个位置。例如(?<!a)b是一个负回顾断言,它会匹配一个不在字符 a 后面的 b 。这个表达式不能匹配字符串 cab,但是可以匹配字符串 bed 和 debt 中的 b。

正回顾的语法是(?<=text),其中text是断言中的表达式。

16.5 引擎是如何处理回顾断言的?

让我们来看一下表达式(?<=a)b是如何匹配字符串 thingamabob 的。正则的第一个token是一个正回顾断言,并且引擎会从字符串的第一个字符开始匹配。引擎一旦遇到回顾断言便会回退一个字符,此时引擎处于字符串中的第一个字符因此无法回退。回顾断言的匹配失败了,因此引擎回到正则的开头重新进行匹配。此次匹配的字符 h,同样引擎在遇到回顾断言后会回退并检查 h 的前一个字符能否匹配tokena,但是字符 h 的前面是字符 t ,回顾断言再次匹配失败。

在之后5轮的回溯中回顾断言都匹配失败,知道第6轮回溯。这次匹配的字符是 m,引擎暂时回退一个位置并且成功地匹配到了回顾断言中的tokena。引擎继续匹配下一个tokenb,因为回顾断言的匹配是零宽的,所以tokenb任然匹配字符 m ,匹配不成功,引擎重新开始回溯。下一轮回溯中回顾断言再次匹配失败,引擎继续回溯。

这次要匹配的字符是 b ,引擎首先匹配回顾断言,并且发现字符 b 前面的 a 可以和回顾断言匹配。接下来引擎用tokenb匹配字符 b ,匹配是成功的。此时整个表达式匹配成功,表示匹配到的结果是字符串中第一个出现的 b。

16.6 回顾断言中的注意点

好消息是回顾断言可以在表达式中的任何位置使用,除了开头位置。如果你想匹配不以字符 s 结尾的字符串,你可以使用\b\w+(?<!s)\b。但是如果你使用\b\w+[^s]\b你会得到完全不同的结果。如果用着两个表达式匹配字符串 John’s ,前一个的匹配结果是 John ,而后一个的匹配结果是 John’。至于其中的原因就留给读者自己思考(提示:第二个\b将匹配单引号和字符 s 之间的位置)。第二个字符不能匹配单个出现的字符,例如 a 或者 b 。如果你不想使用回顾断言,正确的表达式是\b\w*[^s\W]\b。我个人觉得,与回顾断言比较而言,它更难以理解。最后这个表达式,对于引擎来说可以正确的执行并且匹配出准确的结果,它使用了双重否定(它取反字符集中使用了\W)。双重否定对于人来说是难以理解的,甚至在一些引擎中双重否定被当做错误处理。

坏消息是并不是所有的特性都可以在回顾断言中使用,因为断言中的表达式无法回溯。引擎在执行回顾断言之前需要确定回退的字符数量。当引擎执行回顾断言的时候,引擎首先确定断言中的正则所要匹配字符的长度,然后取出当前位置之前指定长度的字符串,最后按正常的匹配原则匹配这个子字符串。

在许多引擎中,你可以在回顾断言中使用字面量字符,转义字符,字符集。但是不能使用量词,回溯引用。你可以使用选择符,但是其中选项长度必须一致。


如果文章出现错误,请给我提Issues - -
Github地址

原文

猜你喜欢

转载自blog.csdn.net/billll/article/details/86411282
今日推荐