正则表达式-量词的三种模式

Greedy 数量词
X? X,一次或一次也没有
X* X,零次或多次
X+ X,一次或多次
X{n} X,恰好 n
X{n,} X,至少 n
X{n,m} X,至少 n 次,但是不超过 m
 
Reluctant 数量词
X?? X,一次或一次也没有
X*? X,零次或多次
X+? X,一次或多次
X{n}? X,恰好 n
X{n,}? X,至少 n
X{n,m}? X,至少 n 次,但是不超过 m
 
Possessive 数量词
X?+ X,一次或一次也没有
X*+ X,零次或多次
X++ X,一次或多次
X{n}+ X,恰好 n
X{n,}+ X,至少 n
X{n,m}+ X,至少 n 次,但是不超过 m

import java.util.Arrays;

public class Test {
    
public static void main(String args[]) {
            String t
="x123xxxxxx123";
            Pattern p
=Pattern.compile(".*123");
            Matcher m
=p.matcher(t);
            System.out.println(
"===========贪婪模式================");
            
while(m.find()){                
                System.out.println(
"开始"+m.start());
                System.out.println(m.group());
                System.out.println(
"结束"+m.end());
            }
           
            
            Pattern p1
=Pattern.compile(".*?123");
            Matcher m1
=p1.matcher(t);
            System.out.println(
"===========懒惰模式================");
            
while(m1.find()){                
                System.out.println(
"开始"+m1.start());
                System.out.println(m1.group());
                System.out.println(
"结束"+m1.end());
            }
            
            
            Pattern p2
=Pattern.compile(".*+123");
            Matcher m2
=p2.matcher(t);
            System.out.println(
"===========侵入模式================");
            
while(m1.find()){                
                System.out.println(
"开始"+m2.start());
                System.out.println(m2.group());
                System.out.println(
"结束"+m2.end());
            }
            
    }
}


以下为输出:
===========贪婪模式================
开始0
x123xxxxxx123
结束13
===========懒惰模式================
开始0
x123
结束4
开始4
xxxxxx123
结束13
===========侵入模式================

讨论:
      贪婪模式:它的特性是一次性地读入整个字符串,如果不匹配就吐掉最右边的一个字符再匹配,直到找到匹配的字符串或字符串的长度为0为止。它的宗旨是读尽可能多的字符,所以当读到第一个匹配时就立刻返回。

      此例中,正则“.*123”中的“.*”首先消耗了整个字符串,所以匹配肯定不成功。然后匹配器一次后退一个字符,一直后退到最右侧出现“123”为止,这里匹配成功并且搜索停止。或者这样理解,为了整个表达式能匹配成功,“.*”虽然可以读入整个表达式,但它还是“主动让出”123这3个它本可读入的字符空间,从这点可看出贪婪模式虽贪婪但还是挺深明大义的。 

 
    懒惰模式:它的特性是从字符串的左边开始,试图不读入字符串中的字符进行匹配,失败,则多读一个字符,再匹配,如此循环,当找到一个匹配时会返回该匹配的字符串,然后再次进行匹配直到字符串结束。
      
      此例中,正则“.*?123”中的“.*?”,它试图0消耗的匹配字符串,但失败,不得不再读入(消耗)一个字符再进行匹配,这时x123符合要求,返回之(x123),再读入,直到末尾时找到xxxxxx123匹配又返回之,这时已经没有东西可读了,结束匹配。
  
      此例还返回
                  开始4
                  xxxxxx123
                  结束13
      而不是最后的x123,这说明为了成功匹配,懒惰的它也不懒惰的多读了一连串x让后面的123匹配。
      侵入模式:它和贪婪模式很相似,不同点是它不会往会吐。
      
      此例中,正则“.*+123”中的“.*+”首先消耗了整个字符串,所以匹配不成功,由于它不回吐,所以匹配结束。

结论:懒惰模式的本性是懒惰的,贪婪模式的本性是贪婪的,但当在它们周围出现其它伙伴时,懒惰模式就不那么懒惰,贪婪模式就不那么贪婪了。

还有一点:
      开始0
      x123
      结束4
      开始4
      xxxxxx123
      结束13
可以看出,一个结束就意味着下一个的开始,这暗示了,这个结束位置的字符并没有被消耗掉(如果消耗掉的话下一个的开始应该是5了)。

 

模式的陷阱:

当一个表达式中,有多个非贪婪匹配时,或者多个未知匹配次数的表达式时,这个表达式将可能存在效率上的陷阱。有时候,匹配速度慢得莫名奇妙,甚至开始怀疑正则表达式是否实用。

效率陷阱的产生

   “如果少匹配就会导致整个表达式匹配失败的时候,与贪婪模式类似,非贪婪模式会最小限度的再匹配一些,以使整个表达式匹配成功。”

    具体的匹配过程是这样的:

  1. "非贪婪部分" 先匹配最少次数,然后尝试匹配 "右侧的表达式"。
  2. 如果右侧的表达式匹配成功,则整个表达式匹配结束。如果右侧表达式匹配失败,则 "非贪婪部分" 将增加匹配一次,然后再尝试匹配 "右侧的表达式"。
  3. 如果右侧的表达式又匹配失败,则 "非贪婪部分" 将再增加匹配一次。再尝试匹配 "右侧的表达式"。
  4. 依此类推,最后得到的结果是 "非贪婪部分" 以尽可能少的匹配次数,使整个表达式匹配成功。或者最终仍然匹配失败。

    当一个表达式中有多个非贪婪匹配,以表达式 "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..." 时,将花费较长一段时间才能判断出匹配失败 。

效率陷阱的避免:

    避免效率陷阱的原则是:避免“多重循环”的“尝试匹配”。并不是说非贪婪匹配就是不好的,只是在运用非贪婪匹配的时候,需要注意避免过多“循环尝试”的问题。

    情况一:对于只有一个非贪婪或者贪婪匹配的表达式来说,不存在效率陷阱。也就是说,要匹配类似 "<td> 内容 </td>" 这样的文本,表达式 "<td>([^<]|<(?!/td>))*</td>" 和 "<td>((?!</td>).)*</td>" 和 "<td>.*?</td>" 的效率是完全相同的。

    情况二:如果一个表达式中有多个未知匹配次数的表达式,应防止进行不必要的尝试匹配。

    比如,对表达式 "<script language='(.*?)'>(.*?)</script>" 来说, 如果前面部分表达式在遇到 "<script language='vbscript'>" 时匹配成功后,而后边的 "(.*?)</script>" 却匹配失败,将导致第一个 ".*?" 增加匹配次数再尝试。而对于表达式真正目的,让第一个 ".*?" 增加匹配成“vbscript'>”是不对的,因此这种尝试是不必要的尝试。

    因此,对依靠边界来识别的表达式,不要让未知匹配次数的部分跨过它的边界。前面的表达式中,第一个 ".*?" 应该改写成 "[^']*"。后边那个 ".*?" 的右边再没有未知匹配次数的表达式,因此这个非贪婪匹配没有效率陷阱。于是,这个匹配脚本块的表达式,应该写成:"<script language='([^']*)'>(.*?)</script>" 更好。


 

posted @ 2009-09-15 09:51  Fskjb  阅读(2178)  评论(0编辑  收藏  举报
年年行好运