量词

星号*和加号+重复性


前面已经引入了一个重复运算符或量词:问号 ? 。它告诉正则引擎尝试将前面的正则匹配零次或一次,其实就是使前面的正则成为可选的,也就是可有可无。 

星号*告诉引擎尝试将先前的正则匹配零次或更多次。加号告诉引擎将先前的正则匹配至少一次或多次。<[A-Za-z][A-Za-z0-9]*>匹配没有任何属性的HTML标记。尖括号是文本字符。第一个字符类与一个字母匹配。第二个字符类别与字母或数字匹配。星号*重复第二个放括号中的字符类。因为我们使用星号,所以第二个字符类不匹配也是可以。因为星号是允许零次匹配的。因此,我们的正则表达式将匹配<B>之类的标记。匹配<HTML>时,第一个字符类将匹配H。星号将使第二个字符类别重复三遍,并在每次重复步骤中依次匹配T,M和L。     

我也可以使用<[ A-Za-z0-9 ]+> 。但是我们并没有这样做,因为此正则表达式会匹配<1> 此类的标记,这不是有效的HTML标记。但是,如果您在写正则之前就已经知道所搜索的字符串不包含任何此类无效标签,则此正则表达式可能就足够了。  

限制重复


上面说到的星号*和加号+都是无限制的重复。还有一个附加的量词,可让我们指定正则可以重复多少次。语法为{min,max},其中min为零或表示最小匹配数的正整数,并且max是等于或大于min的整数,表示最大匹配数。如果存在逗号但省略了max ,则最大匹配数是无限的。因此{0,1}?相同,{0,}*相同,而{1,}+相同。省略逗号和最大值都将告诉引擎重复先前的正则min次。

我们可以使用\b[1-9][0-9]{3}\b来匹配1000和9999之间的数字。\b[1-9][0-9]{2,4}\b可以匹配 100到99999中间的一个数值。

小心贪婪!


假设您要使用正则表达式来匹配HTML标记。您知道输入将是有效的HTML文件,因此正则表达式不需要排除尖括号中的任何无效使用。如果它位于尖括号之间,则为HTML标记。

对正则表达式不熟悉的大多数人都将尝试使用<.+> 。然而当真正使用这个正则表达式去匹配的时候会发现结果大大出乎我们的意料。比如说字符串This is a <EM>first</EM> test。我们可能希望正则表达式匹配<EM>,并且在匹配之后继续匹配</EM> 。     

但事实并非如此。正则表达式将匹配<EM>first</EM> 。显然不是我们想要的。原因就是加号+是贪婪的。也就是说,加号使正则表达式引擎尽可能多地重复前面的正则。只有当导致整个正则表达式失败的时候,正则表达式引擎才会回溯。也就是说,它将返回至加号,使其放弃最后的迭代,然后继续进行正则表达式的其余部分。让我们看一下正则表达式引擎内部,以详细了解它是如何工作的以及为什么这会导致我们的正则表达式不是我们期望的结果。

像加号+一样,星号*和使用花括号的重复也是贪婪的。

正则表达式引擎内部


正则表达式中的第一个标记是<,成功匹配的第一个地方是字符串中的第一个<。下一个标记是点.,它匹配换行符以外的任何字符。点号由加号重复。加号是贪婪的。因此,引擎将尽可能多地重复该点。点与E匹配,因此正则表达式继续尝试将点与下一个字符匹配。M被匹配,并且该点再次重复。下一个字符是> 。您现在应该已经看到了问题。点与>匹配,引擎继续重复该点。点将匹配字符串中所有剩余的字符。当引擎在字符串末尾到达空白时,点将失败。仅在这一点上,正则表达式引擎才继续下一个标记:> 。           

到目前为止,<.+已匹配<EM>first</EM> test,并且引擎已到达字符串的末尾。>在这里不能匹配。引擎知道,加号重复点.的次数比要求的要多的多。(要记住,加号仅要求点匹配一次。)引擎并不会宣告失败,而是回溯。它将把加号的重复次数减少一,然后继续尝试正则表达式的其余部分。         

因此,.+的匹配减少为EM>first</EM> tes。正则表达式中的下一个标记仍然是> 。但是现在字符串中的下一个字符是最后一个t 。再次,这些不能匹配,导致引擎进一步回溯,减少加号的重复次数。到目前为止,总的匹配到的字符串为<EM>first</ EM> te 。但是>仍然无法匹配。因此,引擎将继续回溯,直到将.+的匹配减小为EM>first</EM为止。现在,>可以匹配字符串中的下一个字符>。正则表达式中的最后一个标记已匹配。引擎报告<EM>first</ EM>已成功匹配。               

请记住,正则表达式引擎是急功近利的,所以到这就返回匹配项。它不会继续回溯以查看是否还有其他可能的匹配项。它将报告找到的第一个有效匹配项。由于加号+的贪婪性,返回了最长的匹配。  

懒惰代替贪婪


解决此问题的快速方法是使加号+成为懒惰而不是贪婪。惰性量词有时也称为“不舒服”或“不情愿”。您可以通过在正则表达式中的加号后面加一个问号来做到这一点。我们可以对星号,花括号和问号本身执行相同的操作。因此我们的示例变为<.+?>。让我们再看看正则表达式引擎是如何匹配的。   

同样,<匹配字符串中的第一个<。下一个标记是点.,这次由一个懒惰的加号重复。这告诉正则表达式引擎重复点尽可能少的次数。最小为1。因此,引擎将点与E匹配。要求已经得到满足,并且正则引擎继续>和M。这失败了。同样,引擎将回退。但是这一次,回溯将迫使惰性加号增加而不是减小其重复次数。因此,.+的匹配项增加为EM ,引擎再次尝试以>继续。现在,>已成功匹配。正则表达式中的最后一个标记已匹配。引擎报告<EM>已成功匹配。嗯,这才是我们想要的。                 

不要懒惰,使用更好的方式


在这种情况下,有一个更好的选择,而不是使加号+成为懒惰的。我们可以使用贪婪的加号和否定的字符类:<[^>]+> 。之所以这种方式更好,是由于这种方式避免了正则表达式引擎的回溯。我们知道一定程度的回溯是会影响性能的。使用惰性加号时,引擎必须回溯它试图匹配的HTML标记中的每个字符。使用否定字符类时,当字符串包含有效的HTML代码时,就不会发生回溯。回溯会减慢正则表达式引擎的速度。在文本编辑器中进行单个搜索时,您不会注意到差异。但是,如果在编写的脚本中或者在EditPad Pro的自定义语法着色方案中频繁的循环重复使用此类正则表达式,则可以节省大量CPU周期。   

只有正则导向的引擎会发生回溯。文本导向引擎不会,因此不会有速度的损失。但是有利也有弊,文本导向的引擎是不支持惰性量词的。  

查看笔记

扫码一下
查看教程更方便