正则表达式中的量词

自我感觉量词是正则表达式里最不容易理解的地方,所以特别为它做了个总结。

为了容易理解,会简单地结合正则表达式引擎的工作方式来讲。正则表达式引擎分为文本导向型(Text-directed Engines)和正则表达式导向型(Regex-directed Engines)两种。因为基本上采用的是正则表达式导向型的引擎,所以下文关于引擎工作方式的部分都是基于正则表达式导向型引擎的。


一、没有量词时正则表达式引擎的工作方式

在没有量词之前,正则表达式的一个符号块只能匹配文本中的一个符号,如[abc]匹配字符abc。此时,正则表达式的匹配流程非常的简单。

正则表达式引擎按从左到右的顺序读取正则表达式中的字符块和文本中的字符,并检查字符块和字符是否匹配。根据匹配的结果和匹配符号的位置,后续的操作分为四种。

  1. 匹配成功,且匹配的是正则表达式的第一个符号块。说明文本中以该字符开始的一段字符串可能会是我们需要的字符串,所以引擎接着向右读取正则表达式中的字符块和文本中的字符进行匹配。为了说明的方便,我们把这个字符记为A
  2. 匹配成功,且匹配的是正则表达式的最后一个符号块。说明文本中从A开始到目前读取位置的这一段字符是我们需要的字符串。于是,引擎将这段文本输出,然后接着寻找下一个匹配的字符串,它继续向右读取文本中的字符,但是从头开始读取正则表达式中的字符块,将它们进行匹配。
  3. 匹配成功,且匹配的是正则表达式中间的符号块。说明文本中从A开始到目前为止的这一段字符还是匹配的,如果之后的字符也匹配的话就找到所需的字符串。所以引擎接着向右读取正则表达式中的字符块和文本中的字符进行匹配。
  4. 匹配失败,无论匹配的是正则表达式中的哪个符号块。说明在从文本中从A开始的各种字符串中,并不存在我们所需的字符串。因此,引擎重新从A的后一个位置开始读取字符,并从头开始读取正则表达式中的字符块,进行匹配。

引擎不断重复这样的操作,直到读取到文本的终结符。

比如,我们用正则表达式<[ou]l>去匹配文本<ol>This is ol</ol>。引擎先读取正则表达式的第一个字符块<和文本的第一个字符<。因为它们成功匹配,所以引擎继续读取正则表达式的第二个字符块[ou]和文本的第二个字符o,也成功匹配了,就继续……,直到匹配到>,正则表达式和字符串<ol>完全匹配了,于是找到了第一个我们所需字符串。之后,引擎继续读取文本中的字符T和正则表达式中的第一个字符块<,匹配失败,引擎读取文本中的下一个字符h,还是失败,直到读取到第15个字符<,匹配成功。然后引擎读取正则表达式中的[ou]字符块和文本中的字符块,匹配失败,引擎重新从文本的第15个<之后开始读取字符,从正则表达式的开头读取字符块……直到引擎读到了终结符,查找结束,找到了一个字符串,开始于文本的第1位,结束于文本的第4位。

注意:在这种实现方式下,正则表达式会优先匹配文本最左边的字符串,而且匹配过的内容不会重复出现。如用正则表达式a.{4,4}去匹配My dear grandparent,得到的匹配结果是ar grandpa。其中没有arent,因为arent中的首字母a已经在andpa中被匹配了。这消除了匹配顺序导致的歧义是否能重复匹配导致的歧义


二、量词带来的不确定性

但是,引入了量词之后,事情就变得复杂了起来。量词可以让被修饰的字符重复若干次,如a*表示任意个a组成的字符串。量词在正则表达式中起着很大的作用,但使用中总是出现意想不到的结果。

问题的起因是,被修饰字符的重复次数往往是不确定的。文本中以同一个字符开头,可能会有好多种长度不同的字符串形式与正则表达式匹配,这就引起了歧义。如用<.*>去匹配<p>first</p> yeah,以第一个<打头,可以有两种长度不同的匹配,<p><p>first</p>,到底采用哪一种匹配呢?总不能靠运气吧。于是,在量词的基础上又给他分了三类。

量词有三类,贪婪型量词(Greedy quantifiers),勉强型量词(Reluctant quantifiers)和占有型量词(Possessive quantifiers)。


三、贪婪型量词和勉强型量词

由于引擎按从左往右的顺序读取,它并不能提前预知后面的字符串是什么,也就不知道到底让被修饰字符重复多少次能获得一个匹配的字符串。虽然你一眼看去能看出来,上一个例子中的.*.重复1次匹配文本中的p和重复10次匹配文本中的p>first</p就能获得一个匹配的字符串。但是计算机并不能,他只能一个一个可能性地试过去,他可能从重复次数为0开始尝试,0次不行就1次,1次不行就两次……,也有可能从最大的重复次数开始试,最大的不行就最大减一次……。这样直到获得第一个匹配,那么匹配成功,引擎就把它当作了匹配结果,或者最大到最小的所有的可能都试过了都不行,那么当前这段字符串和正则表达式的匹配就失败了。

有些同学可能对最大重复次数有些迷茫。它就是不考虑后面的符号块能不能匹配的情况下,被修饰字符块能连续匹配多少个文本中的字符。以上一个例子来说,当引擎将正则表达式的<和文本的第一个字符<匹配起来后,读取到正则表达式的.*后,不考虑它之后的>.*可以匹配文本中的p>first</p> yeah,这就是在尝试匹配以文本中第一个字符<开头的字符串时,它的最大重复次数。

从最大重复次数开始逐步减小重复次数的量词是贪婪型量词,而从最小重复次数开始逐步增大重复次数的量词是勉强型量词。

量词默认是贪婪的,贪婪型量词会使被修饰字符重复尽可能多的次数。用以上例子来说明引擎处理贪婪型量词的方式,首先引擎读取了正则表达式的首字符块<和文本的首字符<,并且匹配成功。之后引擎会读取正则表达式的.*.可以匹配所有的字符,而*使.重复出现,而*又是贪婪的,所以引擎会不停地重复用.去匹配文本中的字符,直到读到文本的终结符,.和终结符匹配失败,这个时候.的重复次数达到最大了,而引擎读取了之后的>,发现已经匹配不了了,这个时候引擎知道重复最大次数是不行的,于是让.少重复了一次,.*少匹配了文本中最后的h,引擎拿着这个h和正则表达式的>去匹配,匹配还是不成功,所以这个重复此时还是不行,再减一……直到重复次数为10为止,.*吐出来的>和正则表达式的>成功匹配,而此时正则表达式中的字符块被全部匹配了,结果就产生了。

在贪婪型量词的后面加一个就成了勉强型量词,勉强型量词会使被修饰字符重复尽可能少的次数。对以上例子来说,正则表达式为<.*?>,匹配方式如下。首先引擎读取了正则表达式的首字符块<和文本的首字符<,并且匹配成功。之后引擎读取正则表达式的.*?*?是勉强的,引擎首先会让.重复最小的次数,由于*代表任意次,所以最小次数为0。之后,引擎会读取.*?之后的>,尝试着让它去匹配文本中的下一个字符p,匹配失败,这个时候引擎知道重复最小次数是匹配不了了,于是让.多重复了一次,让.*?匹配了p,之后再用>去匹配文本的下一个字符>,匹配成功,此时此时正则表达式中的字符块被全部匹配了,结果就产生了。


四、占有型量词

在贪婪型量词的后面加一个+就成了占有型量词,占有型量词让被修饰字符重复最大次数。乍一看和贪婪型量词没啥区别啊,其实少了三个字,尽可能。还是用上面的例子来说,此时正则表达式为.*+,引擎只会尝试一种可能,前面的过程和贪婪型相同。当引擎发现.的重复次数达到最大了,引擎读取了之后的>,发现匹配不了,这时引擎知道重复最大次数是不行的,然后整个匹配就失败了,引擎不会去尝试其他的重复次数。

占有型量词可以提高匹配的效率,因为它不会遍历所有可能性去匹配一段字符串,它只匹配重复次数最大的那种可能,行就行,不行就不行。如果我们需要的结果只有在最大重复次数时才会出现,那其余的尝试都是不必要的

比如,我们要得到文本<p>one</p> two中每对尖括号包裹的内容,那我们可能会用<.*?>,但我们也可以用表达式<[^>]*+>。后者效率更高,因为它不用尝试其他的可能性。


五、参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容