之前我们已经介绍过问号?
这个量词,引擎会把它之前的token匹配一次或者零次,换句话说问号之前的token是可选的。
星号的意思是匹配0次或者多次它之前的token,而加号的意思说匹配1次或者多次。例如<[A-Za-z][A-Za-z0-9]*>
可以匹配一个不包含属性的HTML标签。其中尖括号是字面量字符。第一个字符集匹配一个字母。第二个字符集匹配一个字母或是数字。第二个字符集可以重复匹配,因为这里使用了星号所以第二个字符集可以不匹配任何字符。也就是说这个表达式可以匹配单个字母的标签 <B>
,同时也可以匹配 <HTML>
。其中第一个字符集匹配到 H,第二个字符集重复匹配了三次分别匹配到T,M,L。
也可是使用<[A-Za-z0-9]+>
来搜索HTML标签,但是这个表达式的缺点是它也可以匹配到<1>这种非法的标签。
我们可以使用花括号来限定token重复匹配的次数。它的语法是{min,max},其中min是一个大于等于0的整数,而max是大于等于min的整数。如果使用了逗号,
且省略max则表示max是无穷大。所以{0,1}
等同于?
,{0,}
等同于*
,{1,}
等同于+
。如果同时省略逗号和 max,则表示限定重复 min 次。
你可以使用\b[1-9][0-9]{3}\b
匹配1000到9999中的数字。\b[1-9][0-9]{2,4}\b
则可以匹配100到99999之间的整数。注意这里使用了词语边界。
假设你要匹配HTML标签,并且你知道匹配的数据都是合法的标签。也就是测试数据中不包含尖括号非法使用的情况,只要出现在一对尖括号中间的就是HTML标签。
很多正则表达式的新手会首先尝试<.+>
。如果用这个表达式匹配字符串This is a <EM>first</EM> test
的话,他们会得到令人意外的结果。或许你以为他会匹配到<EM>
和</EM>
。
但是结果并非如此,他匹配到的是<EM>first</EM>
。原因就在于加号是贪婪的,也就是说加号使得引擎尽可能多的去重复匹配加号之前的token。也就是说只有当整个表达式匹配失败的时候引擎才开始“回退”,引擎将回到加号并且放弃最后一次迭代,用剩下的表达式匹配剩余的字符串。在下一节中我们详细展开这个过程,在这之后我们会提出两种方法来解决上面遇到的问题。
要注意:和加号一样,星号和花括号也是贪婪的。
在上一个例子中,第一个匹配的token是字面量字符<
,而第一个匹配成功的字符是字符串中的第一个 < 。接下来匹配的token是.
,它可以匹配除了换行符以外的任何字符,它的后面是一个*
,*
可以匹配无限多次并且星号是贪婪的。所以引擎会尽可能多次的匹配。接下来出现的字符是 E 它和.
匹配成功了,所以引擎进一步把下一个字符和.
匹配。E 后面的 M 也匹配成功了。接下来的字符是 > ,现在你因该已经看出问题来了吧。由于 > 也是一个普通字符,所以.
也能匹配它。实际上.
可以匹配所有剩下的字符。直到正则表达式末尾的void,.
才匹配失败。接下来引擎匹配最后一个token>
。
此时正则表达式的前半部分<.+
已经匹配到了字符串中的<EM>first</EM> test
,并且此时引擎将把token>
和字符串中最后一个位置匹配,匹配不能成功。此时引擎知道.
可能不需要重复那么多次,此时引擎不会立即确认匹配失败,而是原路返回一个位置。也就是说引擎会减少一次重复的次数,然后继续匹配表达式的剩余部分。
所以此时.+
匹配到的结果是EM>first</EM> tes
。下一个要匹配的token是>
,但是此时下一个要匹配的字符是 t 。这一次的回退还是没有匹配成功。引擎继续回退到<EM>first</EM> te
,此时还是不能匹配上。又经过5次回退后,引擎已经回退到<EM>first</EM
,此时token>
可以和下一个字符>
匹配。随着最后一个token匹配成功,引擎得到最终的匹配结果<EM>first</EM>
。
由于引擎会返回最先匹配到的结果,所以一旦匹配成功引擎就不会继续回退,即使能匹配到更短的结果。所以在贪婪模式下,引擎会匹配到最靠左且最长的结果。
你可以使用惰性匹配来快速修复这个问题。它的语法是在量词后面添加一个问号。例如.??
,.*?
,.+?
,.{0,1}?
。那么在刚才那里例子中,我们可以这么修改<.+?>
。让我们再看看引擎内部的工作方式。
和之前一样第一个token<
匹配到了字符串中的首个 < 。下一个token是.
,不过这一次.
后面的量词是惰性的。引擎会尽可能少的匹配token.
的次数。这个例子中量词+
至少需要匹配一次。所以.
匹配到了字符 E 。接下来引擎匹配token>
,因为 E 后面是 M ,所以不能匹配上。此时引擎并不能确定匹配失败,由于前一个token是量词,所以此时引擎进行一次回退。但是和贪婪模式不同的是,惰性模式会尝试增加一次重复匹配的.
。在此次退回之后token.+
匹配到字符 EM。接下来引擎把下一个token>
和字符 > 匹配。现在所有的token已经匹配完成,引擎匹配到了字符 <EM>
。我们离正确答案似乎又近了一步。
的确存在比惰性匹配更好的方式,那就是使用字符集取反:<[^>]+>
。这种方式更好的原因是因为它避免了回溯,如果测试数据中的标签都是合法的,使用字符集取反可以避免回溯。回溯会影响引擎的性能,你可能无法再小规模测试中感觉到这种差异,但是如果你在一个循环语句中使用的话那就可能导致引擎崩溃。
文本驱动的引擎不支持回溯功能,所以这种引擎也不支持惰性匹配。
如果文章出现错误,请给我提Issues - - Github地址