正则表达式之匹配优先与忽略优先

概述

正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。

标准匹配量词(?、*、+,以及{min,max})都是“匹配优先(greedy)”的。如果用这些量词来约束某个表达式,例如‘a?’中的‘a’,‘[0-9]+’中的‘[0-9]’,在匹配成功之前,进行尝试的次数是存在上限和下限的。标准匹配量词的结果“可能”并非所有可能中最长的,但它们总是尝试匹配尽可能多的字符,直到匹配上限为止。

而忽略优先,则是在原本的匹配优先量词后加一个问号即可,即在*、+、?、{min,max}后加入?即可。

匹配优先:尽可能多的匹配
忽略优先:尽可能少的匹配

要注意的是,DFA不支持忽略优先。

匹配优先量词:*、+、?、{min,max}
忽略优先量词:*?、+?、??、{min,max}?

1.匹配优先:

我们来看‘^\w+\d’匹配‘1a2b2c2de’的过程。首先‘\w+’匹配整个字符串之后,然后为了匹配‘\d’要求‘\w+’释放一个字符‘e’(最后的字符)。看是否能让‘\d’匹配,如果不能匹配,‘\w+’必须继续“交还”字符,直到能匹配‘\d’为止,如果所有的字符都交还完后还是不能匹配,则匹配失败。

正则匹配忽略换行和空格Java 正则表达式忽略_双引号

2.忽略优先:

而对于忽略优先,总是匹配尽可能少的字符,先用‘\w’来匹配‘1a2b2c2de’中的‘1’,此时,‘+?’又必须选择,是继续进行匹配,还是忽略?因为它是忽略优先的,会首先选择忽略。接下来的‘\d’不能匹配后面的‘a’,所以‘\w+?’会继续尝试未匹配的‘a’,然后看后面的字符能否匹配‘\d’,直到能匹配‘\d’或尝试所有可能后报告匹配失败。

正则匹配忽略换行和空格Java 正则表达式忽略_双引号_02

3.如果用‘^\w+(\d+)’和‘^\w+?(\d+)’来匹配‘copyright2019’,括号会捕获到什么?

‘^\w+(\d+)’匹配时为了满足‘\d+’的匹配,必须交还一些字符。释放的字符是最后的‘9’,之后‘9’能够由‘\d’匹配。‘\d’由‘+’量词进行修饰,所以现在还做到了最小匹配的可能,此时没有“必须”匹配的元素,所以‘\w’不会被迫交出‘1’。匹配优先的结构只会在被迫的情况下交还字符。所以,最终$1的值是‘9’:

正则匹配忽略换行和空格Java 正则表达式忽略_正则表达式_03


‘^\w+?(\d+)’匹配时会先匹配‘copyright2019’中的‘c’,因为它是忽略优先的,会首先选择忽略,接下来‘\d’不能匹配后一个字符‘o’,所以‘\w+’会继续匹配未匹配的‘o’,在这个过程重复9次之后,‘\w+’最终匹配了‘copyright’,此时,接下来的‘2019’都能匹配,所以最终$1的值是‘2019’:

正则匹配忽略换行和空格Java 正则表达式忽略_字符串_04

4.对于匹配双引号文本:

首先想到的可能就是‘ “.*” ’,在最开始的双引号匹配之后,‘.*’能够匹配任何字符,所以它会一直匹配到字符串的末尾。为了让最后的双引号能够匹配,‘.*’不会交还字符(或者更确切的说,是正则引擎强迫它回退),直到满足为止。最后,这个正则表达式的结果就是:

正则匹配忽略换行和空格Java 正则表达式忽略_正则匹配忽略换行和空格Java_05


原本希望匹配到的是"McDonald’s"和"makudonarudo",然而这显然不是我们所期望的结果,我们希望匹配到的不是双引号之间的“任何文本”,而是“除双引号以外的任何文本”。如果用‘[^"]*’替代‘.*’,结果是:

正则匹配忽略换行和空格Java 正则表达式忽略_双引号_06


用排除法时注意,因为在大多数的流派中,‘[^"]*’能够匹配换行符,而点号则不能。

正则匹配忽略换行和空格Java 正则表达式忽略_双引号_07


如果不想让表达式匹配换行符,可以用‘[^"\n]’。

正则匹配忽略换行和空格Java 正则表达式忽略_正则表达式_08


也可以使用忽略优先量词:

正则匹配忽略换行和空格Java 正则表达式忽略_正则表达式_09

5.对于多字符“引文”

与双引号字符的例子一样,使用“.*”匹配多字符引文也会出错,‘<B>.*</B>’中匹配优先‘.*’会一直匹配到该行的结尾字符,回溯只会进行到最后一个‘</B>’,而不是与匹配开头的‘<B>’相对应的‘</B>’:

正则匹配忽略换行和空格Java 正则表达式忽略_双引号_10


我们可以使用忽略优先量词‘<B>.*?</B>’来进行匹配:

正则匹配忽略换行和空格Java 正则表达式忽略_正则匹配忽略换行和空格Java_11


当然,错误的使用忽略优先量词有时候也会出现问题,如果用‘<B>.*?</B>’来匹配‘…<B>Billions and <B> Zillions</B> of suns…’时会出现:

正则匹配忽略换行和空格Java 正则表达式忽略_正则匹配忽略换行和空格Java_12


显然这不是用户所期望的结果,这时候如果支持否定环视,我们就能得到与排除型字符组相当的结果,只有当‘<B>’不在字符串中的当前位置才能匹配成功。

正则匹配忽略换行和空格Java 正则表达式忽略_字符串_13


这时候不管是用匹配优先量词还是忽略优先量词都可以。

测试链接

https://regexr.com