ImageVerifierCode 换一换
格式:DOCX , 页数:15 ,大小:476.41KB ,
资源ID:7650358      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/7650358.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(正则基础之贪婪与非贪婪模式.docx)为本站会员(b****5)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

正则基础之贪婪与非贪婪模式.docx

1、正则基础之贪婪与非贪婪模式1 概述贪婪与非贪婪模式影响的是被量词修饰的子表达式的匹配行为,贪婪模式在整个表达式匹配成功的前提下,尽可能多的匹配,而非贪婪模式在整个表达式匹配成功的前提下,尽可能少的匹配。非贪婪模式只被部分NFA引擎所支持。属于贪婪模式的量词,也叫做匹配优先量词,包括:“m,n”、“m,”、“?”、“*”和“+”。在一些使用NFA引擎的语言中,在匹配优先量词后加上“?”,即变成属于非贪婪模式的量词,也叫做忽略优先量词,包括:“m,n?”、“m,?”、“?”、“*?”和“+?”。从正则语法的角度来讲,被匹配优先量词修饰的子表达式使用的就是贪婪模式,如“(Expression)+”;

2、被忽略优先量词修饰的子表达式使用的就是非贪婪模式,如“(Expression)+?”。对于贪婪模式,各种文档的叫法基本一致,但是对于非贪婪模式,有的叫懒惰模式或惰性模式,有的叫勉强模式,其实叫什么无所谓,只要掌握原理和用法,能够运用自如也就是了。个人习惯使用贪婪与非贪婪的叫法,所以文中都会使用这种叫法进行介绍。2 贪婪与非贪婪模式匹配原理对于贪婪与非贪婪模式,可以从应用和原理两个角度进行理解,但如果想真正掌握,还是要从匹配原理来理解的。先从应用的角度,回答一下“什么是贪婪与非贪婪模式?”2.1 从应用角度分析贪婪与非贪婪模式2.1.1 什么是贪婪与非贪婪模式先看一个例子举例:源字符串:aate

3、st1bbtest2cc正则表达式一:.*匹配结果一:test1bbtest2正则表达式二:.*?匹配结果二:test1(这里指的是一次匹配结果,所以没包括test2)根据上面的例子,从匹配行为上分析一下,什是贪婪与非贪婪模式。正则表达式一采用的是贪婪模式,在匹配到第一个“”时已经可以使整个表达式匹配成功,但是由于采用的是贪婪模式,所以仍然要向右尝试匹配,查看是否还有更长的可以成功匹配的子串,匹配到第二个“”后,向右再没有可以成功匹配的子串,匹配结束,匹配结果为“test1bbtest2”。当然,实际的匹配过程并不是这样的,后面的匹配原理会详细介绍。仅从应用角度分析,可以这样认为,贪婪模式,就

4、是在整个表达式匹配成功的前提下,尽可能多的匹配,也就是所谓的“贪婪”,通俗点讲,就是看到想要的,有多少就捡多少,除非再也没有想要的了。正则表达式二采用的是非贪婪模式,在匹配到第一个“”时使整个表达式匹配成功,由于采用的是非贪婪模式,所以结束匹配,不再向右尝试,匹配结果为“test1”。仅从应用角度分析,可以这样认为,非贪婪模式,就是在整个表达式匹配成功的前提下,尽可能少的匹配,也就是所谓的“非贪婪”,通俗点讲,就是找到一个想要的捡起来就行了,至于还有没有没捡的就不管了。2.1.2 关于前提条件的说明在上面从应用角度分析贪婪与非贪婪模式时,一直提到的一个前提条件就是“整个表达式匹配成功”,为什么

5、要强调这个前提,我们看下下面的例子。正则表达式三:.*bb匹配结果三:test1bb修饰“.”的仍然是匹配优先量词“*”,所以这里还是贪婪模式,前面的“.*”仍然可以匹配到“test1bbtest2”,但是由于后面的“bb”无法匹配成功,这时“.*”必须让出已匹配的“bbtest2”,以使整个表达式匹配成功。这时整个表达式匹配的结果为“test1bb”,“.*”匹配的内容为“test1”。可以看到,在“整个表达式匹配成功”的前提下,贪婪模式才真正的影响着子表达式的匹配行为,如果整个表达式匹配失败,贪婪模式只会影响匹配过程,对匹配结果的影响无从谈起。非贪婪模式也存在同样的问题,来看下面的例子。正

6、则表达式四:.*?cc匹配结果四:test1bbtest2cc这里采用的是非贪婪模式,前面的“.*?”仍然是匹配到“test1”为止,此时后面的“cc”无法匹配成功,要求“.*?”必须继续向右尝试匹配,直到匹配内容为“test1bbtest2”时,后面的“cc”才能匹配成功,整个表达式匹配成功,匹配的内容为“test1bbtest2cc”,其中“.*?”匹配的内容为“test1bbtest2”。可以看到,在“整个表达式匹配成功”的前提下,非贪婪模式才真正的影响着子表达式的匹配行为,如果整个表达式匹配失败,非贪婪模式无法影响子表达式的匹配行为。2.1.3 贪婪还是非贪婪应用的抉择通过应用角度的分

7、析,已基本了解了贪婪与非贪婪模式的特性,那么在实际应用中,究竟是选择贪婪模式,还是非贪婪模式呢,这要根据需求来确定。对于一些简单的需求,比如源字符为“aatest1bb”,那么取得div标签,使用贪婪与非贪婪模式都可以取得想要的结果,使用哪一种或许关系不大。但是就2.1.1中的例子来说,实际应用中,一般一次只需要取得一个配对出现的div标签,也就是非贪婪模式匹配到的内容,贪婪模式所匹配到的内容通常并不是我们所需要的。那为什么还要有贪婪模式的存在呢,从应用角度很难给出满意的解答了,这就需要从匹配原理的角度去分析贪婪与非贪婪模式。2.2 从匹配原理角度分析贪婪与非贪婪模式如果想真正了解什么是贪婪模

8、式,什么是非贪婪模式,分别在什么情况下使用,各自的效率如何,那就不能仅仅从应用角度分析,而要充分了解贪婪与非贪婪模式的匹配原理。2.2.1 从基本匹配原理谈起NFA引擎基本匹配原理参考:正则基础之NFA引擎匹配原理。这里主要针对贪婪与非贪婪模式涉及到的匹配原理进行介绍。先看一下贪婪模式简单的匹配过程。源字符串:Regex正则表达式:.* 图2-1注:为了能够看清晰匹配过程,上面的空隙留得较大,实际源字符串为“”Regex”,下同。来看一下匹配过程。首先由第一个“”取得控制权,匹配位置0位的“”,匹配成功,控制权交给“.*”。“.*”取得控制权后,由于“*”是匹配优先量词,在可匹配可不匹配的情况

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

10、串:Regex正则表达式:.*? 图2-2看一下非贪婪模式的匹配过程。首先由第一个“”取得控制权,匹配位置0位的“”,匹配成功,控制权交给“.*?”。“.*?”取得控制权后,由于“*?”是忽略优先量词,在可匹配可不匹配的情况下,优先尝试不匹配,由于“*”等价于“0,”,所以在忽略优先的情况下,可以不匹配任何内容。从位置1处尝试忽略匹配,也就是不匹配任何内容,将控制权交给正则表达式最后的“”。“”取得控制权后,从位置1处尝试匹配,由“”匹配位置1处的“R”,匹配失败,向前查找可供回溯的状态,控制权交给“.*?”,由“.*?”吃进一个字符,匹配位置1处的“R”,再把控制权交给正则表达式最后的“”。

11、“”取得控制权后,从位置2处尝试匹配,由“”匹配位置1处的“e”,匹配失败,向前查找可供回溯的状态,重复以上过程,直到由“.*?”匹配到“x”为止,再把控制权交给正则表达式最后的“”。“”取得控制权后,从位置6处尝试匹配,由“”匹配字符串最后的“”,匹配成功。此时整个正则表达式匹配成功,其中“.*?”匹配的内容为“Regex”,匹配过程中进行了四次回溯。2.2.2 贪婪还是非贪婪匹配效率的抉择通过匹配原理的分析,可以看到,在匹配成功的情况下,贪婪模式进行了更少的回溯,而回溯的过程,需要进行控制权的交接,让出已匹配内容或匹配未匹配内容,并重新尝试匹配,在很大程度上降低匹配效率,所以贪婪模式与非贪

12、婪模式相比,存在匹配效率上的优势。但2.2.1中的例子,仅仅是一个简单的应用,读者看到这里时,是否会存在这样的疑问,贪婪模式就一定比非贪婪模式匹配效率高吗?答案是否定的。举例:需求:取得两个“”中的子串,其中不能再包含“”。正则表达式一:.*正则表达式二:.*?情况一:当贪婪模式匹配到更多不需要的内容时,可能存在比非贪婪模式更多的回溯。比如源字符串为“The word Regex means regular expression.”。情况二:贪婪模式无法满足需求。比如源字符串为“The phrase regular expression is called Regex for short.”。

13、对于情况一,正则表达式一采用的贪婪模式,“.*”会一直匹配到字符串结束位置,控制权交给最后的“”,匹配不成功后,再进行回溯,由于多匹配的内容“means regular expression.”远远超过需匹配内容本身,所以采用正则表达式一时,匹配效率会比使用正则表达式二的非贪婪模式低。对于情况二,正则表达式一匹配到的是“regular expression is called Regex”,连需求都不满足,自然也谈不上什么匹配效率的高低了。以上两种情况是普遍存在的,那么是不是为了满足需求,又兼顾效率,就只能使用非贪婪模式了呢?当然不是,根据实际情况,变更匹配优先量词修饰的子表达式,不但可以满足

14、需求,还可以提高匹配效率。源字符串:Regex给出正则表达式三:*看一下正则表达式三的匹配过程。 图2-3首先由第一个“”取得控制权,匹配位置0位的“”,匹配成功,控制权交给“*”。“*”取得控制权后,由于“*”是匹配优先量词,在可匹配可不匹配的情况下,优先尝试匹配。从位置1处的“R”开始尝试匹配,匹配成功,继续向右匹配,匹配位置2处的“e”,匹配成功,继续向右匹配,直到匹配到“x”,匹配成功,再匹配结尾的“”时,匹配失败,将控制权交给正则表达式最后的“”。“”取得控制权后,匹配字符串结尾处的“”,匹配成功。此时整个正则表达式匹配成功,其中“*”匹配的内容为“Regex”,匹配过程中没有进行回

15、溯。将量词修饰的子表达式由范围较大的“.”,换成了排除型字符组“”,使用的仍是贪婪模式,很完美的解决了需求和效率问题。当然,由于这一匹配过程没有进行回溯,所以也不需要记录回溯状态,这样就可以使用固化分组,对正则做进一步的优化。给出正则表达式四:(?*)固化分组并不是所有语言都支持的,如.NET支持,而Java就不支持,但是在Java中却可以使用更简单的占有优先量词来代替:*+。3 贪婪还是非贪婪模式再谈匹配效率一般来说,贪婪与非贪婪模式,如果量词修饰的子表达式相同,比如“.*”和“.*?”,它们的应用场景通常是不同的,所以效率上一般不具有可比性。而对于改变量词修饰的子表达式,以满足需求时,比如

16、把“.*”改为“*”,由于修饰的子表达式已不同,也不具有直接的可对比性。但是在相同的子表达式,又都可以满足需求的情况下,比如“*”和“*?”,贪婪模式的匹配效率通常要高些。同时还有一个事实就是,非贪婪模式可以实现的,通过优化量词修饰的子表达式的贪婪模式都可以实现,而贪婪模式可以实现的一些优化效果,却未必是非贪婪模式可以实现的。贪婪模式还有一点优势,就是在匹配失败时,贪婪模式可以更快速的报告失败,从而提升匹配效率。下面将全面考察贪婪与非贪婪模式的匹配效率。3.1 效率提升演进过程在了解了贪婪与非贪婪模式的匹配基本原理之后,我们再来重新看一下正则效率提升的演进过程。需求:取得两个“”中的子串,其中

17、不能再包含“”。源字符串:The phrase regular expression is called Regex for short.正则表达式一:.*正则表达式一匹配的内容为“regular expression is called Regex”,不符合要求。提出正则表达式二:.*?首先“”取得控制权,由位置0位开始尝试匹配,直到位置11处匹配成功,控制权交给“.*?”,匹配过程同2.2.1中非贪婪模式的匹配过程。“.*?”匹配的内容为“Regex”,匹配过程中进行了四次回溯。如何消除回溯带来的匹配效率的损失,就是使用更小范围的子表达式,采用贪婪模式,提出正则表达式三:*首先“”取得控制

18、权,由位置0位开始尝试匹配,直到位置11处匹配成功,控制权交给“*”,匹配过程同2.2.2节中非贪婪模式的匹配过程。“*”匹配的内容为“Regex”,匹配过程中没有进行回溯。3.2 效率提升更快的报告失败以上讨论的是匹配成功的演进过程,而对于一个正则表达式,在匹配失败的情况下,如果能够以最快的速度报告匹配失败,也会提升匹配效率,这或许是我们设计正则过程中最容易忽略的。而在源字符串数据量非常大,或正则表达式比较复杂的情况下,是否能够快速报告匹配失败,将对匹配效率产生直接的影响。下面将构建匹配失败的正则表达式,对匹配过程进行分析。以下匹配过程分析中,源字符串统一为:The phrase regul

19、ar expression is called Regex for short.3.2.1 非贪婪模式匹配失败过程分析 图3-1构建匹配失败的非贪婪模式的正则表达式:.*?由于最后的“”的存在,这个正则表达式最后一定是匹配失败的,那么看一下匹配过程。首先由“”取得控制权,由位置0处开始尝试匹配,匹配失败,直到图中标示的A处匹配成功,控制权交给“.*?”。“.*?”取得控制权后,由A后面的位置开始尝试匹配,由于是非贪婪模式,首先忽略匹配,将控制权交给“”,同时记录一下回溯状态。“”取得控制权后,由A后面的位置开始尝试匹配,匹配字符“r”失败,查找可供回溯的状态,将控制权交给“.*?”,由“.*?

20、”匹配字符“r”。重复以上过程,直到“.*?”匹配了B处前面的字符“n”,“”匹配了B处的字符“”,将控制权交给“”。由“”匹配接下来的空格“ ”,匹配失败,查找可供回溯的状态,控制权交给“.*?”,由“.*?”匹配空格。继续重复以上匹配过程,直到由“.*?”匹配到字符串结束位置,将控制权交给“”。由于已经是字符串结束位置,匹配失败,报告整个表达式在位置11处匹配失败,一轮匹配尝试结束。正则引擎传动装置使正则向前传动,进入下一轮尝试。后续匹配过程与第一轮尝试匹配过程基本类似,可以参考图3-1。从匹配过程中可以看到,非贪婪模式的匹配失败过程,几乎每一步都伴随着回溯过程,对匹配效率的影响是很大的。

21、3.2.2 贪婪模式匹配失败过程分析大范围子表达式 图3-2PS:以上分析过程图示参考了精通正则表达式一书相关章节图示。构建匹配失败的贪婪模式的正则表达式:.*其中量词修饰的子表达式为匹配范围较大的“.”,由于最后的“”的存在,这个正则表达式最后也是一定匹配失败的,看一下匹配过程。首先由“”取得控制权,由位置0处开始尝试匹配,匹配失败,直到图中标示的A处匹配成功,控制权交给“.*”。“.*”取得控制权后,由A后面的位置开始尝试匹配,由于是贪婪模式,优化尝试匹配,一直匹配到字符串的结束位置,将控制权交给“”。“”取得控制权后,由于已经是字符串的结束位置,匹配失败,查找可供回溯的状态,将控制权交给

22、“.*”,由“.*”让出已匹配字符“.”。重复以上过程,直到后面“”匹配了C处后面的字符“”,将控制权交给“”。由“”匹配接下来D处的空格“ ”,匹配失败,查找可供回溯的状态,控制权交给“.*”,由“.*”让出已匹配文本。继续重复以上匹配过程,直到由“.*”让出所有已匹配的文本到I处,将控制权交给“”。“”匹配失败,由于已经没有可供回溯的状态,报告整个表达式在位置11处匹配失败,一轮匹配尝试结束。正则引擎传动装置使正则向前传动,进入下一轮尝试。后续匹配过程与第一轮尝试匹配过程基本类似,可以参考图3-2。从匹配过程中可以看到,大范围子表达式贪婪模式的匹配失败过程,从总体上看,与非贪婪模式没有什么

23、区别,最终进行的回溯次数与非贪婪模式基本一致,对匹配效率的影响仍然很大。3.2.3 贪婪模式匹配失败过程分析改进的子表达式 图3-3构建匹配失败的贪婪模式的正则表达式:*其中量词修饰的子表达式,改为匹配范围较小的排除型字符组“”,由于最后的“”的存在,这个正则表达式最后也是一定匹配失败的,看一下匹配过程。首先由“”取得控制权,由位置0处开始尝试匹配,匹配失败,直到图中标示的A处匹配成功,控制权交给“*”。“*”取得控制权后,由A后面的位置开始尝试匹配,由于是贪婪模式,优先尝试匹配,一直匹配到B处,将控制权交给“”。“”匹配接下来的的字符“”,匹配成功,将控制权交给“”。由“”匹配接下来的空格“

24、 ”,匹配失败,查找可供回溯的状态,控制权交给“*”,由“*”让出已匹配文本。继续重复以上匹配过程,直到由“*”让出所有已匹配的文本到C处,将控制权交给“”。“”匹配失败,由于已经没有可供回溯的状态,报告整个表达式在位置11处匹配失败,一轮匹配尝试结束。正则引擎传动装置使正则向前传动,进入下一轮尝试。后续匹配过程与第一轮尝试匹配过程基本类似,可以参考图3-3。从匹配过程中可以看到,使用了排除型字符组的贪婪模式的匹配失败过程,从总体上看,大量减少了每轮回溯的次数,可以有效的提升匹配效率。3.2.4 贪婪模式匹配失败过程分析固化分组通过3.2.3节的分析可以知道,由于“*”使用了排除型字符组,那么

25、图3-3中,在A和B之间被匹配到的字符,就一定不会是字符“”,所以B到C之间回溯过程就是多余的,也就是说在这之间的可供回溯的状态完全可以不记录。.NET中可以使用固化分组,Java中可以使用占有优先量词来实现这一效果。 图3-4首先由“”取得控制权,由位置0处开始尝试匹配,匹配失败,直到图中标示的A处匹配成功,控制权交给“(?*)”。“(?*)”取得控制权后,由A后面的位置开始尝试匹配,由于是贪婪模式,优先尝试匹配,一直匹配到B处,将控制权交给“”,在这一匹配过程中,不记录任何可供回溯的状态。“”匹配接下来的字符“”,匹配成功,将控制权交给“”。由“”匹配接下来的空格“ ”,匹配失败,查找可供

26、回溯的状态,由于已经没有可供回溯的状态,报告整个表达式在位置11处匹配失败,一轮匹配尝试结束。正则引擎传动装置使正则向前传动,进入下一轮尝试。后续匹配过程与第一轮尝试匹配过程基本类似,可以参考图3-4。从匹配过程中可以看到,使用了固化分组的贪婪模式的匹配失败过程,没有涉及到回溯,可以最大限度的提升匹配效率。3.3 非贪婪模式向贪婪模式的转换使用匹配范围较大的子表达式时,贪婪模式与非贪婪模式匹配到的内容会有所不同,但是通过优化子表达式,非贪婪模式可以实现的匹配,贪婪模式都可以实现。比如在实际应用中,匹配img标签的内容。举例:需求:取得img标签中的图片地址,src=后固定为“”源字符串:正则表

27、达式一:匹配结果中,捕获组1的内容即为图片地址。可以看到,这个例子中使用的都是非贪婪模式,而根据上面章节的分析,后面两个非贪婪模式都可以使用排除型字符组,将非贪婪模式转换为贪婪模式。正则表达式二:*注:“src=”和标签结束标记符“”之间的属性中,也可能出现字符“”,但那是极端情况,这里不予讨论。后两处非贪婪模式,可以通过排除型字符组转换为贪婪模式,提高匹配效率,而“src=”前的非贪婪模式,由于要排除的是一个字符序列“src=”,而不是单独的某一个或几个字符,所以不能使用排除型字符组。当然也不是没有办法,可以使用顺序环视来达到这一效果。正则表达式三:*“(?!src=).”表示这样一个字符,

28、从它开始,右侧不能是字符序列“src=”,而“(?:(?!src=).)*”就表示符合上面规则的字符,有0个或无限多个。这样就达到排除字符序列的目的,实现的效果同排除型字符组一样,只不过排除型字符组排除的是一个或多个字符,而这种环视结构排除的是一个或多个有序的字符序列。但是以顺序环视的方式排除字符序列,由于在匹配每一个字符时,都要进行较多的判断,所以相对于非贪婪模式,是提升效率还是降低效率,要根据实际情况进行分析。对于简单的正则表达式,或是简单的源字符串,一般来说是非贪婪模式效率高些,而对于数量较大源字符串,或是复杂的正则表达式,一般来说是贪婪模式效率高些。比如上面取得img标签中的图片地址需

29、求,基本上用正则表达二就可以了;对于复杂的应用,如平衡组中,就需要使用结合环视的贪婪模式了。以匹配嵌套div标签的平衡组为例:Regex reg = new Regex(?isx) #匹配模式,忽略大小写,“.”匹配任意字符 * #开始标记“” (? #分组构造,用来限定量词“*”修饰范围 * (?) #命名捕获组,遇到开始标记,入栈,Open计数加1 | #分支结构 (?) #狭义平衡组,遇到结束标记,出栈,Open计数减1 | #分支结构 (?:(?!/?divb).)* #右侧不为开始或结束标记的任意字符 )* #以上子串出现0次或任意多次 (?(Open)(?!) #判断是否还有OPEN,有则说明不配对,什么都不匹配 #结束标记“” );“(?:(?!/?divb).)*”这里使用的就是结合环视的贪婪模式

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1