权后,由A后面的位置开始尝试匹配,由于是贪婪模式,优先尝试匹配,一直匹配到B处,将控制权交给“"”,在这一匹配过程中,不记录任何可供回溯的状态。“"”匹配接下来的字符“””,匹配成功,将控制权交给“@”。由“@”匹配接下来的空格“ ”,匹配失败,查找可供回溯的状态,由于已经没有可供回溯的状态,报告整个表达式在位置11处匹配失败,一轮匹配尝试结束。
正则引擎传动装置使正则向前传动,进入下一轮尝试。后续匹配过程与第一轮尝试匹配过程基本类似,可以参考图3-4。
从匹配过程中可以看到,使用了固化分组的贪婪模式的匹配失败过程,没有涉及到回溯,可以最大限度的提升匹配效率。
3.3 非贪婪模式向贪婪模式的转换
使用匹配范围较大的子表达式时,贪婪模式与非贪婪模式匹配到的内容会有所不同,但是通过优化子表达式,非贪婪模式可以实现的匹配,贪婪模式都可以实现。
比如在实际应用中,匹配img标签的内容。
举例:
需求:取得img标签中的图片地址,src=后固定为“””
源字符串:<img class="test" src="/uploadfile/201306/29/01101643718.gif" title="测试" />
正则表达式一:<img\b.*?src="(.*?)".*?>
匹配结果中,捕获组1的内容即为图片地址。可以看到,这个例子中使用的都是非贪婪模式,而根据上面章节的分析,后面两个非贪婪模式都可以使用排除型字符组,将非贪婪模式转换为贪婪模式。
正则表达式二:<img\b.*?src="([^"]*)"[^>]*>
注:“src="…"”和标签结束标记符“>”之间的属性中,也可能出现字符“>”,但那是极端情况,这里不予讨论。
后两处非贪婪模式,可以通过排除型字符组转换为贪婪模式,提高匹配效率,而“src=”前的非贪婪模式,由于要排除的是一个字符序列“src=”,而不是单独的某一个或几个字符,所以不能使用排除型字符组。当然也不是没有办法,可以使用顺序环视来达到这一效果。
正则表达式三:<img\b(?:(?!src=).)*src="([^"]*)"[^>]*>
“(?!src=).”表示这样一个字符,从它开始,右侧不能是字符序列“src=”,而“(?:(?!src=).)*”就表示符合上面规则的字符,有0个或无限多个。这样就达到排除字符序列的目的,实现的效果同排除型字符组一样,只不过排除型字符组排除的是一个或多个字符,而这种环视结构排除的是一个或多个有序的字符序列。
但是以顺序环视的方式排除字符序列,由于在匹配每一个字符时,都要进行较多的判断,所以相对于非贪婪模式,是提升效率还是降低效率,要根据实际情况进行分析。对于简单的正则表达式,或是简单的源字符串,一般来说是非贪婪模式效率高些,而对于数量较大源字符串,或是复杂的正则表达式,一般来说是贪婪模式效率高些。
比如上面取得img标签中的图片地址需求,基本上用正则表达二就可以了;对于复杂的应用,如平衡组中,就需要使用结合环视的贪婪模式了。
以匹配嵌套div标签的平衡组为例:
Regex reg = new Regex(@"(?isx) #匹配模式,忽略大小写,“.”匹配任意字符
<div[^>]*> #开始标记“<div...>”
(?> #分组构造,用来限定量词“*”修饰范围
<div[^>]*> (?<Open>) #命名捕获组,遇到开始标记,入栈,Open计数加1
| #分支结构
</div> (?<-Open>) #狭义平衡组,遇到结束标记,出栈,Open计数减1
| #分支结构
(?:(?!</?div\b).)* #右侧不为开始或结束标记的任意字符
)* #以上子串出现0次或任意多次
(?(Open)(?!)) #判断是否还有''OPEN'',有则说明不配对,什么都不匹配
</div> #结束标记“</div>”
");
“(?:(?!</?div\b).)*”这里使用的就是结合环视的贪婪模式,虽然每匹一个字符都要做很多判断,但这种判断是基于字符的,速度很快,而