【转载】理解正则表达式的贪婪与非贪婪模式

概述

贪婪与非贪婪模式影响的是被量词修饰的子表达式的匹配行为,贪婪模式在整个表达式匹配成功的前提下,尽量多地匹配,而非贪婪模式在整个表达式匹配成功的前提下,尽量少地匹配。非贪婪模式只被部分NFA引擎(肯定性有穷自动机)所支持。 正则表达式

属于贪婪模式的量词,也叫作匹配优先量词,包括:spa

1 “{m,n}?”、“{m,}?”、“??”、“*?”、“+?”

从正则语法的角度来说,被匹配优先量词修饰的子表达式使用的就是贪婪模式,如“(Expression)+”;被忽略优先量词修饰的子表达式使用的就是非贪婪模式,如(Expression)+?。对于贪婪模式,各类文档的叫法基本一致,可是对于非贪婪模式,有的叫懒惰模式或惰性模式,有的叫勉强模式。其实叫什么无所谓,只要掌握原理和用法,可以运用自如就好。我的习惯使用贪婪与非贪婪的叫法,因此文中都会使用这种叫法进行介绍。.net

贪婪与非贪婪模式匹配原理

对于贪婪与非贪婪模式,能够从应用和原理两个角度进行理解,但若是想真正掌握,仍是要从匹配原理来理解。先从应用的角度,回答一下“什么是贪婪非贪婪模式”。先看一个例子,源字符串:code

before<div>block one</div>middle<div>block two</div>after

正则表达式一:blog

1 <div>.*</div> 

匹配结果一:three

<div>block one</div>middle<div>block two</div>

正则表达式二:文档

1 <div>.*?</div> 

匹配结果二:字符串

<div>block one</div>(这里指的是一次匹配结果,因此没包括<div>block two</div>

根据上面的例子,从匹配行为上分析一下,什是贪婪与非贪婪模式。正则表达式一采用的是贪婪模式,在匹配到第一个</div>时已经可使整个表达式匹配成功,可是因为采用的是贪婪模式,因此仍然要向右尝试匹配,查看是否还有更长的能够成功匹配的子串,匹配到第二个</div>后,向右再没有能够成功匹配的子串,匹配结束,匹配结果为<div>block one</div>block two<div>block three</div>。固然,实际的匹配过程并非这样的,后面的匹配原理会详细介绍。仅从应用角度分析,能够这样认为,贪婪模式,就是在整个表达式匹配成功的前提下,尽量多地匹配,也就是所谓的“贪婪”,通俗点讲,就是看到想要的,有多少就匹配多少,除非再也没有想要的了。正则表达式二采用的是非贪婪模式,在匹配到第一个</div>时使整个表达式匹配成功,因为采用的是非贪婪模式,因此结束匹配,再也不向右尝试,匹配结果为<div>block one</div>。仅从应用角度分析,能够这样认为,非贪婪模式,就是在整个表达式匹配成功的前提下,尽量少地匹配,也就是所谓的“非贪婪”,通俗点讲,就是找到一个想要的捡起来就好了,至于还有没有没捡的就无论了。io

关于前提条件的说明。在上面从应用角度分析贪婪与非贪婪模式时,一直提到的一个前提条件就是“整个表达式匹配成功”,为何要强调这个前提,咱们看下下面的例子。class

正则表达式三:

1 <div>.*</div>middle

匹配结果三:

<div>block one</div>middle

修饰“.”的仍然是匹配优先量词“*”,因此这里仍是贪婪模式,前面的<div>.*</div>仍然能够匹配到<div>block one</div>middle<div>block two</div>,可是因为后面的middle没法匹配成功,这时<div>.*</div>必须让出已匹配的middle<div>block two</div>,以使整个表达式匹配成功。这时整个表达式匹配的结果为<div>block one</div>middle<div>.*</div>匹配的内容为<div>block one</div>。能够看到,在“整个表达式匹配成功”的前提下,贪婪模式才真正地影响着子表达式的匹配行为,若是整个表达式匹配失败,贪婪模式只会影响匹配过程,对匹配结果的影响无从谈起。非贪婪模式也存在一样的问题,来看下面的例子。 

正则表达式四:

1 <div>.*?</div>after

匹配结果四:

<div>block one</div>middle<div>block two</div>after 

这里采用的是非贪婪模式,前面的<div>.*?</div>仍然是匹配到<div>block one</div>为止,此时后面的after没法匹配成功,要求<div>.*?</div>必须继续向右尝试匹配,直到匹配内容为<div>block one</div>middle<div>block two</div>时,后面的after才能匹配成功,整个表达式匹配成功,匹配的内容为<div>block one</div>middle<div>block two</div>after,其中<div>.*?</div>匹配的内容为<div>block one</div>middle<div>block two</div>。能够看到,在“整个表达式匹配成功”的前提下,非贪婪模式才真正的影响着子表达式的匹配行为,若是整个表达式匹配失败,非贪婪模式没法影响子表达式的匹配行为。

经过应用角度的分析,已基本了解了贪婪与非贪婪模式的特性,那么在实际应用中,到底是选择贪婪模式,仍是非贪婪模式呢,这要根据需求来肯定。对于一些简单的需求,好比源字符为before<div>block one</div>middle,那么取得div标签,使用贪婪与非贪婪模式均可以取得想要的结果,使用哪种或许关系不大。可是就上述例子来讲,在实际应用中,通常一次只须要取得一个配对出现的div标签,也就是非贪婪模式匹配到的内容,贪婪模式所匹配到的内容一般并非咱们所须要的。那为何还要有贪婪模式的存在呢,从应用角度很难给出满意的解答了,这就须要从匹配原理的角度去分析贪婪与非贪婪模式。

下面从匹配原理角度分析贪婪与非贪婪模式。若是想真正了解什么是贪婪模式,什么是非贪婪模式,分别在什么状况下使用,各自的效率如何,那就不能仅仅从应用角度分析,而要充分了解贪婪与非贪婪模式的匹配原理。NFA引擎匹配原理,这里主要针对贪婪与非贪婪模式涉及到的匹配原理进行介绍。先看一下贪婪模式简单的匹配过程。

采用源字符串:"Regex"
采用正则表达式:.*

 

来看一下匹配过程。首先由第一个“””取得控制权,匹配位置0位的“””,匹配成功,控制权交给“.*”。“.*”取得控制权后,因为“*”是匹配优先量词,在可匹配可不匹配的状况下,优先尝试匹配。从位置1处的“R”开始尝试匹配,匹配成功,继续向右匹配,匹配位置2处的“e”,匹配成功,继续向右匹配,直到匹配到结尾的“””,匹配成功,因为此时已匹配到字符串的结尾,因此“.*”结束匹配,将控制权交给正则表达式最后的“””。“””取得控制权后,因为已经在字符串结束位置,匹配失败,向前查找可供回溯的状态,控制权交给“.*”,由“.*”让出一个字符,也就是字符串结尾处的“””,再把控制权交给正则表达式最后的“””,由“””匹配字符串结尾处的“””,匹配成功。此时整个正则表达式匹配成功,其中“.*”匹配的内容为“Regex”,匹配过程当中进行了一次回溯。接下来看一下非贪婪模式简单的匹配过程。

采用源字符串:"Regex"
采用正则表达式:.*?

 

看一下非贪婪模式的匹配过程。首先由第一个“””取得控制权,匹配位置0位的“””,匹配成功,控制权交给“.*?”。“.*?”取得控制权后,因为“*?”是忽略优先量词,在可匹配可不匹配的状况下,优先尝试不匹配,因为“*”等价于“{0,}”,因此在忽略优先的状况下,能够不匹配任何内容。从位置1处尝试忽略匹配,也就是不匹配任何内容,将控制权交给正则表达式最后的“””。“””取得控制权后,从位置1处尝试匹配,由“””匹配位置1处的“R”,匹配失败,向前查找可供回溯的状态,控制权交给“.*?”,由“.*?”吃进一个字符,匹配位置1处的“R”,再把控制权交给正则表达式最后的“””。“””取得控制权后,从位置2处尝试匹配,由“””匹配位置1处的“e”,匹配失败,向前查找可供回溯的状态,重复以上过程,直到由“.*?”匹配到“x”为止,再把控制权交给正则表达式最后的“””。“””取得控制权后,从位置6处尝试匹配,由“””匹配字符串最后的“””,匹配成功。 此时整个正则表达式匹配成功,其中“.*?”匹配的内容为“Regex”,匹配过程当中进行了五次回溯。

经过匹配原理的分析,能够看到,在匹配成功的状况下,贪婪模式进行了更少的回溯,而回溯的过程,须要进行控制权的交接,让出已匹配内容或匹配未匹配内容,并从新尝试匹配,在很大程度上下降匹配效率,因此贪婪模式与非贪婪模式相比,存在匹配效率上的优点。上述例子中,仅仅是一个简单的应用,读者看到这里时,是否会存在这样的疑问,贪婪模式就必定比非贪婪模式匹配效率高吗?答案是否认的。

转自:https://blog.csdn.net/u014762221/article/details/68953155

相关文章
相关标签/搜索