• 开心菜鸟笔记系列---正则表达式笔记(入门篇)


                            开心菜鸟笔记系列---正则表达式笔记(入门篇)                             

    一、基础篇:           

       在支持ASCII码的语言中,如JavaScript,“w”等价于[a-zA-Z0-9_] ;   在支持Unicode的语言中,如.NET,默认情况下,“w”除可以匹配[a-zA-Z0-9_]外,还可以匹配一些Unicode字符集,如汉字,全角数字等等。  

     在Java中,“w”的表现是比较奇怪的,Java是支持Unicode的,但Java的正则中的“w”却是等价于[a-zA-Z0-9_]的。   

     基础应用   “”一般应用在需要匹配某一单词字符组成的子串,但这一字符不能包含在同样由单词字符组成的更长的子串中。

     比如要替换掉一段英文中的单词“to”,而“today”显然不在替换的范围内,所以正则可以用“to”来限定。   

    用得比较多的场景是在HTML标签的匹配中,用以区分相互包含的标签,比如要过滤掉<b>、</b>、<p…>、<img…>等标签,但要保留<br />标签,正则可以写成“<(/?b|p|img)[^>]*>”。                     

    二、正则表达式NFA引擎匹配原理:  

    1)正则引擎大体上可分为不同的两类:DFA和NFA,而NFA又基本上可以分为传统型NFA和POSIX NFA。  

       DFA:     DFA引擎因为不需要回溯,所以匹配快速,但不支持捕获组,所以也就不支持反向引用和$number这种引用方式,目前使用DFA引擎的语言和工具主要有awk、egrep 和 lex。  

       NFA:     POSIX NFA主要指符合POSIX标准的NFA引擎,它的特点主要是提供longest-leftmost匹配,也就是在找到最左侧最长匹配之前,它将继续回溯。同DFA一样,非贪婪模式或者说忽略优先量词对于POSIX NFA同样是没有意义的。    

      DFA不支持的功能:  

        捕获组、反向引用和$number引用方式;     

       环视(Lookaround,(?<=…)、(?<!…)、(?=…)、(?!…)),或者有的有文章叫做预搜索;

        忽略优化量词(??、*?、+?、{m,n}?、{m,}?),或者有的文章叫做非贪婪模式;

        占有优先量词(?+、*+、++、{m,n}+、{m,}+,目前仅Java和PCRE支持),固化分组(?>…)。   

       2)占有字符:  

        正则表达式匹配过程中,如果子表达式匹配到的是字符内容,而非位置,并被保存到最终的匹配结果中,那么就认为这个子表达式是占有字符的;   

     3)、零宽度:    如果子表达式匹配的仅仅是位置,或者匹配的内容并不保存到最终的匹配结果中,那么就认为这个子表达式是零宽度的。       

        占有字符是互斥的,也就是一个字符,同一时间只能由一个子表达式匹配。    零宽度是非互斥的,而一个位置,却可以同时由多个零宽度的子表达式匹配。  

      4)、 控制权和传动    

      正则的匹配过程,通常情况下都是由一个子表达式(可能为一个普通字符、元字符或元字符序列组成)取得控制权,从字符串的某一位置开始尝试匹配,

      一个子表达式开始尝试匹配的位置,是从前一子表达匹配成功的结束位置开始的。

      如正则表达式:    (子表达式一)(子表达式二)    假设(子表达式一)为零宽度表达式,由于它匹配开始和结束的位置是同一个,如位置0,那么(子表达式二)是从位置0开始尝试匹配的。    假设(子表达式一)为占有字符的表达式,由于它匹配开始和结束的位置不是同一个,如匹配成功开始于位置0,结束于位置2,那么(子表达式二)是从位置2开始尝试匹配的。    而对于整个表达式来说,通常是由字符串位置0开始尝试匹配的。如果在位置0开始的尝试,匹配到字符串某一位置时整个表达式匹配失败,那么引擎会使正则向前传动,整个表达式从位置1开始重新尝试匹配,依此类推,直到报告匹配成功或尝试到最后一个位置后报告匹配失败。

       5)、量词‘?’:   

       两种情况:

           字符串:abc     正则:ab?c     

        a:?量词匹配成功:      

          由a开始获取控制权,开始匹配a,匹配成功,交出控制权 b?,?量词会优先匹配,b?匹配b成功,控制权交给c,同时记下备选状态,c去匹配c,匹配成功。      此时正则表达式匹配完成,报告匹配成功。     字符串:ac     正则:ab?c   

        b:?量词匹配失败:      

          由a开始获取控制权,开始匹配a,匹配成功,交出控制权b?,?量词会优先匹配,b?,,同时记下备选状,匹配c,匹配失败,此时进行回溯,找到备选状态,“b?”忽略匹配 控制权交给C,c去匹配,匹配成功,备选状态丢失。      此时正则表达式匹配完成,报告匹配成功。    

       c、量词??:

            源字符串:abc     

         正则表达式:ab??c    

         量词“??”属于忽略优先量词,在可匹配可不匹配时,会先选择不匹配,只有这种选择会使整个表达式无法匹配成功时,才会尝试进行匹配。这里的量词“??”是用来修饰字符“b”的,所以“b??”是一个整体。     匹配过程:     首先由字符“a”取得控制权,从位置0开始匹配,由“a”来匹配“a”,匹配成功,控制权交给字符“b??”;先尝试忽略匹配,即“b??”不进行匹配,同时记录一个备选状态,控制权交给“c”;由“c”来匹配“b”,匹配失败,此时进行回溯,找到记录的备选状态,“b??”尝试匹配,即“b??”来匹配“b”,匹配成功,把控制权交给“c”;由“c”来匹配“c”,匹配成功。     此时正则表达式匹配完成,报告匹配成功。匹配结果为“abc”,开始位置为0,结束位置为3。其中“b??”匹配字    零宽带匹配:     源字符串:a12     正则表达式:^(?=[a-z])[a-z0-9]+$     元字符“^”和“$”匹配的只是位置,顺序环视“(?=[a-z])”只进行匹配,并不占有字符,也不将匹配的内容保存到最终的匹配结果,所以都是零宽度的。     这个正则的意义就是匹配由字母或数字组成的,第一个字符是字母的字符串。     

      d、匹配过程:     

        首先由元字符“^”取得控制权,从位置0开始匹配,“^”匹配的就是开始位置“位置0”,匹配成功,控制权交给顺序环视“(?=[a-z])”;     “(?=[a-z])”要求它所在位置右侧必须是字母才能匹配成功,零宽度的子表达式之间是不互斥的,即同一个位置可以同时由多个零宽度子表达式匹配,所以它也是从位置0尝试进行匹配,位置0的右侧是字符“a”,符合要求,匹配成功,控制权交给“[a-z0-9]+”;     因为“(?=[a-z])”只进行匹配,并不将匹配到的内容保存到最后结果,并且“(?=[a-z])”匹配成功的位置是位置0,所以“[a-z0-9]+”也是从位置0开始尝试匹配的,“[a-z0-9]+”首先尝试匹配“a”,匹配成功,继续尝试匹配,可以成功匹配接下来的“1”和“2”,此时已经匹配到位置3,位置3的右侧已没有字符,这时会把控制权交给“$”;     元字符“$”从位置3开始尝试匹配,它匹配的是结束位置,也就是“位置3”,匹配成功。     此时正则表达式匹配完成,报告匹配成功。匹配结果为“a12”,开始位置为0,结束位置为3。其中“^”匹配位置0,“(?=[a-z])”匹配位置0,“[a-z0-9]+”匹配字符串“a12”,“$”匹配位置3。             

    三 、环视表达式:

        匹配的结果不保存到最终的匹配结果,叫零宽带断言。   环视匹配的的最终结果就是一个位置   

            表达式                        说明  

             (?<=Expression)          逆序肯定环视,表示所在位置左侧能够匹配Expression   

             (?<!Expression)        逆序否定环视,表示所在位置左侧不能匹配Expression  

             (?=Expression)         顺序肯定环视,表示所在位置右侧能够匹配Expression   

             (?!Expression)          顺序否定环视,表示所在位置右侧不能匹配Expression      

        一、顺序环视:   

         对于顺序肯定环视(?=Expression)来说,当子表达式Expression匹配成功时,(?=Expression)匹配成功,并报告(?=Expression)匹配当前位置成功。   对于顺序否定环视(?!Expression)来说,当子表达式Expression匹配成功时,(?!Expression)匹配失败;当子表达式Expression匹配失败时,(?!Expression)匹配成功,并报告(?!Expression)匹配当前位置成功;   顺序环视相当于在当前位置右侧附加一个条件,所以它的匹配尝试是从当前位置开始的,然后向右尝试匹配,直到某一位置使得匹配成功或失败为止。   顺序环视尝试匹配的起点是确定的,就是当前位置,而匹配的终点是不确定的。     

         二、逆序环视:  

         对于逆序肯定环视(?<=Expression)来说,当子表达式Expression匹配成功时,(?<=Expression)匹配成功,并报告(?<=Expression)匹配当前位置成功。   对于逆序否定环视(?<!Expression)来说,当子表达式Expression匹配成功时,(?<!Expression)匹配失败;当子表达式Expression匹配失败时,(?<!Expression)匹配成功,并报告(?<!Expression)匹配当前位置成功;   

        而逆序环视的特殊处在于,它相当于在当前位置左侧附加一个条件,所以它不是在当前位置开始尝试匹配的,而是从当前位置左侧某一位置开始,匹配到当前位置为止,报告匹配成功或失败。   逆序环视匹配的起点是不确定的,是当前位置左侧某一位置,而匹配的终点是确定的,就是当前位置。   长度确定时,引擎可以向左查找固定长度的位置作为起点开始尝试匹配,而如果长度不确定时,就要从当前位置向左逐个位置开始尝试匹配,不成功则回溯,再向左侧位置进行尝试匹配,然后重复以上过程,直到匹配成功,或是尝试到位置0处以后,报告匹配失败,     “{m}”没有放在匹配优先量词中,同样的,“{m}?”虽然被部分语言所支持,但是也没有放在忽略优先量词中,   主要是因为这两种量词,实现的效果是一样的,只有被修饰的子表达式匹配m次才能匹配成功,且没有可供回溯的状态,   所以也不存在是匹配优先还是忽略优先的问题,也就不在本文的讨论范围内。事实上即使讨论也没有意义的,只要知道它们的匹配行为也就是了     

     例子:    (?s)/*(?<=/*).*?(?=*/)*/:    匹配注释

     四、 贪婪和非贪婪:    

      正则表达式:<div>.*</div>   

        字符串:aa<div>test1</div>bb<div>test2</div>cc

      贪婪与非贪婪模式影响的是被量词修饰的子表达式的匹配行为,贪婪模式在整个表达式匹配成功的前提下,尽可能多的匹配,而非贪婪模式在整个表达式匹配成功的前提下,尽可能少的匹配。非贪婪模式只被部分NFA引擎所支持。   

       正则表达式:<div>.*</div>   

       字符串:aa<div>test1</div>bb<div>test2</div>cc   

      被忽略优先量词修饰的子表达式使用的就是非贪婪模式       

       正则表达式:

          <div>.*</div>bb 贪婪

          <div>.*?</div>cc 非贪婪   

      字符串:aa<div>test1</div>bb<div>test2</div>cc  

       总结:   

        贪婪模式才真正的影响着子表达式的匹配行为,如果整个表达式匹配失败,贪婪模式只会影响匹配过程,对匹配结果的影响无从谈起。  

         非贪婪模式才真正的影响着子表达式的匹配行为,如果整个表达式匹配失败,非贪婪模式无法影响子表达式的匹配行为。  

         在一情况,贪婪是效率更高,而有的情况非贪婪效率是高于,那么平时写正则,即要考虑到正则的准确性,又要考虑效率,毕竟正则效率很低。  

         "[^"]"这样效率就高的多了.   它们的应用场景通常是不同的,所以效率上一般不具有可比性。   

        贪婪模式还有一点优势,就是在匹配失败时,贪婪模式可以更快速的报告失败,从而提升匹配效率。  

     以上讨论的是匹配成功的演进过程,而对于一个正则表达式,在匹配失败的情况下,如果能够以最快的速度报告匹配失败,也会提升匹配效率,这或许是我们设计正则过程中最容易忽略的。   一般来说是非贪婪模式效率高些,而对于数量较大源字符串,或是复杂的正则表达式,一般来说是贪婪模式效率高些。            

    五、 捕获组:   

      捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,   方便后面引用。当然,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部。   普通捕获组:(Expression)      都支持   命名捕获组:(?<name>Expression)     命名捕获组目前只有.NET、PHP、Python等部分语言支持。

    另外需要说明的一点是,除(Expression)和(?<name>Expression)语法外,其它的(?...)语法都不是捕获组。   

       一、普通捕获组:    组的编号是按照“(”出现的顺序,从左到右,从1开始进行编号的 。      

      

      

       二、再按命名捕获组中“(”出现的先后顺序,从左到右

       三、混合方式的捕获组编号:

        首先按照普通捕获组中“(”出现的先后顺序,从左到右,从1开始进行编号,当普通捕获组编号完成后,   再按命名捕获组中“(”出现的先后顺序,从左到右,接着普通捕获组的编号值继续进行编号。  

     对捕获组的引用一般有以下几种:

        1)       正则表达式中,对前面捕获组捕获的内容进行引用,称为反向引用;

        2)       正则表达式中,(?(name)yes|no)的条件判断结构;

        3)       在程序中,对捕获组捕获内容的引用。

                                                          2013-10-19

                                                             交流 QQ群:130719662

  • 相关阅读:
    题解 P2280 【[HNOI2003]激光炸弹】
    线段树求逆序对
    题解 P3378 【【模板】堆】
    动态规划-最大算式 蓝桥杯ALGO-116
    动态规划-树形动态规划-结点选择
    Trie树(字典树)-题解 P2580 【于是他错误的点名开始了】
    清北学堂-DAY2-数论专题-中国剩余定理(CRT)
    听课笔记--DP--Authentication Failed
    听课笔记--DP--最大子矩阵和
    多媒体基础
  • 原文地址:https://www.cnblogs.com/kaixincainiao/p/3378346.html
Copyright © 2020-2023  润新知