`
gk23
  • 浏览: 176351 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

(转)正则表达式中贪婪和非贪婪(惰性)匹配的区别与效率问题:

阅读更多

转自:http://hi.baidu.com/%B3%BF%B7%E7%CA%BF%B4%F3%B7%F2/blog/item/f8892c38724feb2e97ddd8cb.html
什么是贪婪模式匹配?
正则中“贪婪模式匹配”原则是: 当解释器将代码中的字符解析成一个个的 编译器在处理代码时眼中看到的最小语法单元时,编译器会使用一种贪婪匹配算法,也就是说会尽可能让一个单元包含更多的字符。
非贪婪匹配的效率
可能有不少的人和我一样,有过这样的经历:当我们要匹配类似 "<td>内容</td>" 或者 "加粗" 这样的文本时,我们根据正向预搜索功能写出这样的表达式:"<td>([^<]|<(?!/td>))*</td>" 或者 "<td>((?!</td>).)*</td>"。
当发现非贪婪匹配之时,恍然大悟,同样功能的表达式可以写得如此简单:"<td>.*?</td>"。 顿时间如获至宝,凡是按边界匹配的地方,尽量使用简捷的非贪婪匹配 ".*?"。特别是对于复杂的表达式来说,采用非贪婪匹配 ".*?" 写出来的表达式的确是简练了许多。
然而,当一个表达式中,有多个非贪婪匹配时,或者多个未知匹配次数的表达式时,这个表达式将可能存在效率上的陷阱。有时候,匹配速度慢得莫名奇妙,甚至开始怀疑正则表达式是否实用。

效率陷阱的产生:
非贪婪匹配:“如果少匹配就会导致整个表达式匹配失败的时候,与贪婪模式类似,非贪婪模式会最小限度的再匹配一些,以使整个表达式匹配成功。”
具体的匹配过程是这样的:
"非贪婪部分" 先匹配最少次数,然后尝试匹配 "右侧的表达式"。
如果右侧的表达式匹配成功,则整个表达式匹配结束。如果右侧表达式匹配失败,则 "非贪婪部分" 将增加匹配一次,然后再尝试匹配 "右侧的表达式"。
如果右侧的表达式又匹配失败,则 "非贪婪部分" 将再增加匹配一次。再尝试匹配 "右侧的表达式"。
依此类推,最后得到的结果是 "非贪婪部分" 以尽可能少的匹配次数,使整个表达式匹配成功。或者最终仍然匹配失败。
当一个表达式中有多个非贪婪匹配,以表达式 "d(\w+?)d(\w+?)z" 为例,对于第一个括号中的 "\w+?" 来说,右边的 "d(\w+?)z" 属于它的 "右侧的表达式",对于第二个括号中的 "\w+?" 来说,右边的 "z" 属于它的 "右侧的表达式"。
当 "z" 匹配失败时,第二个 "\w+?" 会 "增加匹配一次",再尝试匹配 "z"。如果第二个 "\w+?" 无论怎样 "增加匹配次数",直至整篇文本结束,"z" 都不能匹配,那么表示 "d(\w+?)z" 匹配失败,也就是说第一个 "\w+?" 的 "右侧" 匹配失败。此时,第一个 "\w+?" 会增加匹配一次,然后再进行 "d(\w+?)z" 的匹配。循环前面所讲的过程,直至第一个 "\w+?" 无论怎么 "增加匹配次数",后边的 "d(\w+?)z" 都不能匹配时,整个表达式才宣告匹配失败。
其实,为了使整个表达式匹配成功,贪婪匹配也会适当的“让出”已经匹配的字符。因此贪婪匹配也有类似的情况。当一个表达式中有较多的未知匹配次数的表达式 时,为了让整个表达式匹配成功,各个贪婪或非贪婪的表达式都要进行尝试减少或增加匹配次数,由此容易形成一个大循环的尝试,造成了很长的匹配时间。本文之 所以称之为“陷阱”,因为这种效率问题往往不易察觉。
举例:"d(\w+?)d(\w+?)d(\w+?)z" 匹配 "ddddddddddd..." 时,将花费较长一段时间才能判断出匹配失败 。

(?:pattern)
匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。
(?=pattern)
正向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如, 'Windows (?=95|98|NT|2000)' 能匹配 "Windows 2000" 中的 "Windows" ,但不能匹配 "Windows 3.1" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern)
负向预查,在任何不匹配Negative lookahead matches the search string at any point where a string not matching pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始

效率陷阱的避免:
避免效率陷阱的原则是:避免“多重循环”的“尝试匹配”。并不是说非贪婪匹配就是不好的,只是在运用非贪婪匹配的时候,需要注意避免过多“循环尝试”的问题。
情况一:对于只有一个非贪婪或者贪婪匹配的表达式来说,不存在效率陷阱。也就是说,要匹配类似 "<td> 内容 </td>" 这样的文本,表达式 "<td>([^<]|<(?!/td>))*</td>" 和 "<td>((?!</td>).)*</td>" 和 "<td>.*?</td>" 的效率是完全相同的。
情况二:如果一个表达式中有多个未知匹配次数的表达式,应防止进行不必要的尝试匹配。
比如,对表达式 "<script language='(.*?)'>(.*?)</script>" 来说, 如果前面部分表达式在遇到 "<script language='javascript'>" 时匹配成功后,而后边的 "(.*?)</script>" 却匹配失败,将导致第一个 ".*?" 增加匹配次数再尝试。而对于表达式真正目的,让第一个 ".*?" 增加匹配成“javascript'>”是不对的,因此这种尝试是不必要的尝试。
因此,对依靠边界来识别的表达式,不要让未知匹配次数的部分跨过它的边界。前面的表达式中,第一个 ".*?" 应该改写成 "[^']*"。后边那个 ".*?" 的右边再没有未知匹配次数的表达式,因此这个非贪婪匹配没有效率陷阱。于是,这个匹配脚本块的表达式,应该写成:"<script language='([^']*)'>(.*?)</script>" 更好。

分享到:
评论

相关推荐

    正则表达式_贪婪与惰性

    在正则表达式中,量词的贪婪与惰性特性是关键的概念,它们决定了匹配模式的行为方式。贪婪量词和惰性量词的区别在于如何处理重复的匹配。 1. **贪婪量词**: 贪婪量词尽可能多地匹配字符。在正则表达式中,"+"和"*...

    正则表达式中文手册(图书).pdf

    其中还包括贪婪匹配和惰性匹配的概念,贪婪匹配尽可能多地匹配字符,而惰性匹配则尽可能少地匹配字符。 正则表达式还提供了匹配边界的功能,如单词边界和文本边界,这些可以帮助正则表达式在正确的位置开始和结束...

    正则表达式详细介绍

    在不同的编程环境和工具中,正则表达式引擎的类型可能会有所区别。有些引擎是正则导向的(NFA),而另一些则是文本导向的(DFA)。正则导向引擎由于具有“惰性”量词和反向引用等特性,在处理复杂模式时具有更好的...

    javascript正则表达式迷你书 (1).pdf

    * 量词:简写形式、贪婪匹配与惰性匹配 * 多选分支 正则表达式字符匹配攻略 正则表达式中的字符匹配攻略是指使用特殊的字符或符号来匹配字符串中的特定模式。例如,`.`字符可以匹配任何单个字符,而`[abc]`字符组...

    《正则表达式中文手册》

    正则表达式是一种强大而灵活的文本处理工具,广泛应用于多种编程语言中,用于文本的搜索、替换和提取等操作。掌握正则表达式的使用对于任何从事软件开发工作的人员来说都是极其重要的。 #### 二、什么是正则表达式...

    javascript正则表达式详解

    此外,正则表达式还支持贪婪匹配和惰性匹配,贪婪匹配尽可能多地匹配字符,而惰性匹配则尽可能少地匹配字符。多选分支则允许在一组中选择任一匹配项。 位置匹配涉及到对字符串的特定位置进行匹配。正则表达式中的...

    正则表达式(java).rar

    正则表达式是编程语言中用于模式匹配和字符串处理的强大工具,尤其在Java中,它提供了丰富的API来支持正则表达式的使用。本教程将深入浅出地介绍Java中的正则表达式,帮助初学者快速掌握这一核心技能。 1. **正则...

    深入浅出正则表达式.doc

    **定义**:正则表达式(Regular Expression),简称 Regex 或 RegEx,是一种强大的文本模式匹配工具,广泛应用于文本搜索和替换操作中。它可以精确地描述一系列符合某种句法规则的字符串集合。 **应用场景**: - **...

    PHP 正则表达式效率 贪婪、非贪婪与回溯分析(推荐)

    在编写和使用正则表达式时,了解贪婪与非贪婪匹配的概念至关重要,这会直接影响到正则表达式的效率以及匹配结果。在PHP中,这两种模式可通过在量数元字符(如+、*和?等)后面添加一个问号(?)来控制。具体来说,...

    小议正则表达式效率 贪婪、非贪婪与回溯

    在探讨正则表达式的效率问题时,不得不提到贪婪匹配和非贪婪匹配的概念,以及它们所引发的回溯机制。为了深入理解这一机制,我们首先需要明确什么是贪婪匹配,什么是非贪婪匹配。 贪婪匹配指的是,在使用正则表达式...

    js正则表达式惰性匹配和贪婪匹配用法分析

    理解贪婪匹配和惰性匹配的区别对于编写正确的正则表达式非常重要。在JavaScript中,如果需要从字符串中提取信息,可以通过惰性匹配来控制提取的长度,避免不必要的数据干扰。而贪婪匹配则适用于需要尽可能多地匹配...

    正则表达式惰性匹配模式(?)

    正则表达式惰性匹配模式: 在贪婪匹配模式一章节已经说过人性是贪婪的,希望获得更多的金钱、地位甚至美女,但是也有很多清心寡欲的人,只要满足基本的生活需求就可以了,在正则表达式中也有这样的匹配原则,下面就...

    正则表达式系统教程(各种编程语言都有)

    通过阅读和实践,你将能够熟练地运用正则表达式解决实际问题,提升代码效率和质量。 为了充分利用这个资源,建议按照以下步骤学习: 1. 阅读基础概念,理解正则表达式的语法结构。 2. 学习各个编程语言中正则...

    浅出之正则表达式

    正则表达式是一种强大的文本处理工具,用于匹配、查找、替换和分析字符串。它是通过一套特定的语法来定义模式的,这些模式可以描述各种复杂的文本结构。正则表达式(Regex)是Regular Expression的简称。 1. 正则...

    正则表达式最佳入门,正宗资料

    正则表达式(Regular Expression,简称Regex)是用于匹配字符串中模式的一种强大的工具,它广泛应用于数据验证、文本检索和替换等场景。正则表达式的基本概念是通过一系列符号和运算符来描述文本的模式,使得我们...

Global site tag (gtag.js) - Google Analytics