zoukankan      html  css  js  c++  java
  • 正则表达式

    正则表达式以前用过,但是从来没有系统的总结过,今天就正则表达式,及一些细节进行一些总结。

    正则表达式(Regular Expression):是一个用来匹配包含元字符(通配字符)的一个字符串

    元字符:

    基本元字符:  .(任意的一个非换行字符)

      [字符](匹配一个在中括号中的字符)

      | (优先级最低)

      () 用来修改优先级,和分组

                限定元字符:  +表示紧跟在前面的字符出现一次到多次  {1}

                  * 表示紧跟在前面的字符出现0次到多次 {0}

          ? 表示紧跟在前面的字符出现0次到1次  {0,1}

         {n} 表示紧跟在前面的字符出现n

         {n,} 表示紧跟在前面的字符出现n次到多次

         {n,m} 表示紧跟在前面的字符出现n次到m

        首尾元字符: ^  表示必须以某个字符开头或否定

               $表示必须以某个字符结尾或分组引用

     

     在C#中如何使用正则表达式

    -> C#中的核心功能为

    -> 匹配

    -> 提取

    -> 替换

    -> 使用

    -> 引入命名空间 System.Text.RegualrExpressions;

    -> 使用Regex这个类提供的静态方法

    -> 匹配:bool Regex.IsMatch(要匹配的文本正则表达式)

    -> 提取:

    Match Regex.Match(要处理的字符串正则表达式)

    MatchCollection Regex.Matches(要处理的字符串正则表达式)

    -> 替换:

    string Regex.Replace(要处理的字符串正则表达式替换为字符串)

     

     提取组

    -> 给一个字符串,将所有的图片地址提取出来

    -> 在正则表达式中,每一个圆括号是一个分组,在匹配的结果中组会单独的存储

    分组是有编号的,编号从左往右数,凡是看到"("记一个编号,编号从1开始

    匹配完成以后,使用<Match>Groups属性获得组的集合

    编号中第几组就用<Match>.Groups[]进行访问

     

    这些理论的东西很多网上都有,下面我们看一下demo

     /// <summary>
            /// 匹配demo
            /// </summary>
            public static void Match()
            {
                string str = "2014-02-10";
                string reg = @"^\d{4}(-\d{2}){2}$";
    
                if (Regex.IsMatch(str, reg))
                {
                    Console.WriteLine("匹配成功!");
                }
                else
                {
                    Console.WriteLine("匹配失败!");
                }
                Console.ReadKey();
            }
    /// <summary>
            /// 提取
            /// </summary>
            public static void Extract()
            {
                string str = "今天是2014-02-10,天气晴天,明天是2014-02-11,天气小雨。";
                string reg = @"\d{4}(-\d{2}){2}";
    
                //提取第一个匹配的字符串
                Match m= Regex.Match(str,reg);
                if (m.Success)
                {
                    Console.WriteLine("匹配的值   {0}", m.Value);
                    Console.WriteLine("匹配的索引 {0}", m.Index);
                    Console.WriteLine("匹配的长度 {0}", m.Length);
                }
                else
                {
                    Console.WriteLine("不能成功匹配");
                }
                Console.WriteLine("==========================");
                //提取所有匹配的字符串
                MatchCollection m1= Regex.Matches(str, reg);
                
                for (int i = 0; i < m1.Count; i++)
                {
                    if (m1[i].Success)
                    {
                        Console.WriteLine("匹配的值   {0}", m1[i].Value);
                        Console.WriteLine("匹配的索引 {0}", m1[i].Index);
                        Console.WriteLine("匹配的长度 {0}", m1[i].Length);
                    }
                    else
                    {
                        Console.WriteLine("未能成功匹配i={0}",i);
                    }
                }
    
                Console.ReadKey();
            }

    由于上面的代码比较简单,在这里笔者就不贴结果图了。

    我们一起看下提取组:

     /// <summary>
            /// 提取组
            /// </summary>
            public static void ExtractGroup()
            {
                string html = @"
                <td>
                    <img alt="" src='hotgirls/00_02.jpg' /></td>
                <td>
                    <img alt="" src='hotgirls/00_03.jpg' /></td>";
    
                string reg = @"src='(.+)'";
    
                MatchCollection m1 = Regex.Matches(html,reg);
    
                for (int i = 0; i < m1.Count; i++)
                {
                    Console.WriteLine(m1[i].Value);
                    Console.WriteLine(m1[i].Groups[0].Value);
                    Console.WriteLine(m1[i].Groups[1].Value);
                    Console.WriteLine("=============================");
                }
                
                Console.ReadKey();
    
            }

    上面的reg的正则表达式只有一个左圆括弧,所以有一个分组,我们来看下结果

    我们发现m1[i].Value的值和m1[i].Groups[0].Value值是一样的,没错,其实他们就是同一个值,我们要获取的图片的路径也拿到了(没有标记的就是),是通过m1[i].Groups[1].Value值获取到的,这个第几组我们是可以通过数括弧数出来, string reg = @"src='(.+)'"; 我们看见上面有一个圆括弧,所有我们就知道第1组的数据就是我们要的图片路径。

       贪婪模式

    ->  string s = "12345";

    string r = "(\d+)(\d+)";

    从左往右权级递减,正则表达式中只有两个权级,

    一个是尽可能匹配,一个是尽可能不匹配

    -> 文件目录的案例

    string file = @"C:\123\4565\789\abcd.mp3";

            string reg = @"((.:)\\.+)\\((.+)(\..+))";//利用贪婪模式写分组正则

            string reg = @"((.:)(\\.+)?\\)((.+)(\..+))";//能够匹配c:\abcd.mp3

     

    贪婪模型的demo笔者就不写代码了,用一个正则测试工具Code Architects Regex Tester演示一下。

    我们可以看见上面有3个圆括号,所以我们分组的话可以分成3(0组一般不算),第1组匹配的数据明显要多于第二组和第三组,原因是第一组都把字符给吃掉了,但是总是要留一下给第2组第3组吃,所以留最少的,第二组最少有一个字符,第三组有一个字符,所以第一组吃到了5,第二组吃到了6,第三组吃到了7.

    那由此我们猜测一下:如果正则表达式变成了(\d+)(\d*)(\d+)这个,那么第二组最少吃0个字符,第三组吃1个字符,所以我们猜测的结论是 

    第一组 1,2,3,4,5,6

    第二组 

    第三组 7

    我们看下结果

    这个结果我们猜测的是一样的,由此得到结论:从左往右权级递减,正则表达式中只有两个权级,一个是尽可能匹配,一个是尽可能不匹配。

    下面关于贪婪模式的一些介绍:摘自其他人的博客,他们写的非常好

     

    如何取消贪婪模式呢?例如:(\d+)(\d*)(\d+)取消贪婪模式 只要在对于的正则上加一个?  (\d+)?(\d*)(\d+)  这样第一个(\d+)的贪婪模式就被取消掉了,但是第二个又变成贪婪模式了

         参考博文:http://blog.csdn.net/myan/article/details/1520033

        

    深入正则表达式原理 

      、先说正则表达式(regular expression)的基础语法:元字符(metacharacters)和常用的正则表达式              

     1.   点号:匹配单个任意的字符(包括空格,换行符,回车符,制表符,换页符)              

     2.  字符组 [  ] 匹配单个字符。如[abdcedf]也就是匹配[]中的任一个字符都能成功:匹配a成功匹配b也能成功,但不能匹配k这样没有在[]中出现的字符。                

     3.  排除型字符组  [^ ]  排除括号内列出的字符 如[^abcd]匹配e能够成功,匹配a就失败                

     4.   转义字符   \  通常是把元字符转化成普通的字符   通常用于转换如:在匹配.号时,正常情况下正则表达式引擎(会在后面谈到)会把.当做元字符来处理,但是加上\就可以把\.中的点号当做一个普通的字符来处理。               

     5.   问号  ? 匹配一个或没有的字符                 

     6.   星号 *   匹配多个或没有的字符                 

     7.   加号 +  至少匹配一个字符或多个                 

     8.  区间词{minmax} 匹配至少min次,和至多max                 

     9.    ^         匹配一行的开头                 

     10.  $       匹配一行的结尾                 

     11. \b        匹配单词的分界符                  

     12. {?=}   肯定顺序环视                  

     13.  {?!}    否定顺序环视                   

     14.  {?<=}   肯定逆向环视                    

     15.   {?<!}  否定逆向环视                     

     16.  {?:}    非捕获分组                     

     17.   {?>}    固化分组 二、下面的正则表达式针对各种语言又有一些的变化:                       

        1.  \d   匹配任意的数字                       

        2.  \D 匹配任意的非数字                       

        3.  \w 匹配任意的小写字母,大写字母和数字和下划线_等于[a-zA-Z0-9_]                        

        4.  \W 匹配任意的非字母等于[^\s]                          

        5.  \s  匹配空字符如空格符,制表符,等                           

        6.  \S  匹配非空字符                         

        7.   \1\2 主要用于分组获得所分的组项,有时用的很方便哦。               

     还有就是一些常用的匹配表达式:                            

      1.   \n   匹配换行符                             

      2.  \A  匹配一行的开头与^一样的功能 三、 上面的都是些简单的语法,我相信大多数都能懂,那我就讲一些难懂的内容吧:     

         (1) .正则表达式的核心是正则表达式引擎处理方式。一般正则表达式引擎分为两种:NFADFA 但其中的NFA又有两个分支:传统的NFAPOSIX NFA 以下是各种语言所支持的引擎:  

        传统NFAGNU Emacsjavagreplessmore.NETPerlPHPPythonRubysed                        

                  DFA  awkegrepflexMySQLProcmail                       

                  POSIX NFAmawkMortice Kern等等。                        

                 DFA/NFA混合引擎:GNU awk GNU grep/egrepTcl      

      (2).  正则表达式真正起作用是由于这些引擎来计算匹配。所以要想弄懂正则表达式的内部机制,就必须要明白你所熟悉的语言(java.net用的都是传统的NFA)是采用的那种引擎。每一种语言都有这自己引擎,也就有自己的独特的规范和独特的匹配原则。所以正则表达式说采用的匹配结果也不一样。

       但不管是DFA还是NFA他们又有一些共性原则如:                

                  1. 优先匹配最左端(最靠开头)的匹配结果。                 

                 2.标准的匹配量词(*+、?和{minmax})是匹配优先的。        

                     1* 先来说第一条:最左端匹配结果:                  

              如果你用:cat来匹配下面的一句话:  The dragging belly indicates that your cat is too fat.   你本想匹配其中的cat,但是结果却不是,而是indicates中的cat。单词cat 是能够被匹配出来,但是indicates中的cat出现 的更早,所以就先匹配成功了。对于java程序员来说,他只关心是否能匹配而不关心在哪匹配。再来一个例子: 用:fat | cat | belly | your来匹配字符串上面的那一句话。结果会是什么呢?  你认为会是最后的fat是吗?因为fat在 fat | cat | belly | your最前面,是吗?错:答案是belly,句中的belly,为什么呢?由于匹配是从句中的第一个字母T来一次匹配fat | cat | belly | your,中的所有选项,结果发现都不符合,就走到h来匹配,结果也不符合,就依次走下去,直到b字母,有一项符合,就是fat | cat | belly | your中的belly中的b,所以依次匹配发现成功了,所以就正确了。就不在向后面的单词匹配(这种结果只是在支持NFA的引擎中)         

         2*  再来说说第二条:标准量词(*+)是匹配优先(greedy 贪婪的意思)的。从英文单词中,我们就可以发现一些原理。其实的确就是这样。   \b\w+s\b 来匹配包含s的字符串,如regexes,整个是能够比配成功的,你或许会认为它是正则表达式\w+匹配到xe结束然后让s来匹配最后的regexes中的s,若你这样想,那就错了,其实\w+一次性的匹配到结束包括s在内都被它给匹配了,但后面有个s所以,她很不情愿的吐出一个s来,让后面的s来匹配。这样使得匹配成功。这个+是不是很greedy啊?像欧也妮~葛朗台是吗?O(∩_∩)O哈哈~,对了,就是,这条规则很重要,对于正则表达式的优化很重要。很多时候就是在这个基础上进行优化的。   下面就再来比较一下DFANFA的区别。   一般DFA速度较快,而NFA的控制能力较强。这两者各有千秋。  NFA引擎是表达式主导(有点困了,还是下次再写吧,那先留个目录,有助于下次写的:下面主要是NFA的工作原理,不同regular Expression 的流派,然后就是正则表达式的优化和调校,再者就是正则表达式的书写和案例经典例子的回放,最后是写我最爱的Java语言的正则表达式的应用) 

     

    一、上篇中说到NFA正则引擎的表达式主导:                   

    (1).  为什么说NFA是表达式主导,请看下面的例子:      用 to(nite | knight | night) 来匹配 " I am going to seeing film tonight,could you go with me? "这段文本。                            

     NFA引擎会从文本中的第一个字母I开始匹配,它会把匹配表达式全部走一遍,然后发现没有成功,则正则表达式引擎会驱动向下走,到达第二个字符-空格,发现整个匹配表达式也没有匹配成功,则继续,然后到a,依次类推,直到“tonight"这个to,成功匹配后,转向文本中tonight中的n,发现(nite)符合,这它不会再向下匹配,返回成功,然后转向tonight中的i,发现也能成功,则它也不会继续向下匹配,然后转向文本tonight中的g,到正则表达式中的(nite)发现t不匹配,则此时正则引擎会回退从knight单词的k字母进行匹配,发现也不符合,则回退,再从night单词中的n开始匹配,发现符合,则继续匹配,然后到i,发现匹配再到g,依次类推直到匹配成功。最后返回匹配成功。你明白没?其实表达式的控制权在不同的元素字母之间转换,所以我们称之为表达式主导”                                  

     (2). DFA引擎的文本主导:  继续我们的这个例子。  但我们进行I字母的匹配时不成功,就不说了,一直到tonight单词是,前面的to比配成功后,当进行to(nite | knight | night)中的n进行匹配时,DFA会同时向三个选项发出匹配请求,有(nitenight)两个符合,(发现没是同时发出请求)则就会抛弃knight选项,以后就不会再进行匹配,然后是i字母的匹配,发现nitenight都符合,则继续,到tonight中的g,在DFA中同时再向nitenight发出匹配请求,发现只有night符合,就会抛弃nite这个选项,然后继续向下匹配。知道成功。发现没,这种方法少了NFA中的很多的回退,所以效率高。。。。      

    二、有现在主流的语言都是用的是NFA正则引擎,那我们就来重点介绍它的实现机制:   要想了解其中的原理,我们先了解几个概念:回溯、备用状态  专业术语是回溯其实在我的理解就是回退。NFA引擎最很重要的性质是:她会一次处理各个表达式,或组成元素,遇到需要在两个可能成功中进行选择的时候,她会选择其一,同时记住另一个,以备稍后的可能的需要。   并且重要的是:不论选择那一种途径,如果它能匹配成功,而且正则表达式的余下部分也能匹配成功,此时才宣告最终匹配成功,或许你觉得我这个说的是废话。但你请看下面的,如果正则表达式中余下的部分最终匹配失败,引擎会知道需要回溯到之前作出选择的地方,选择其他的备用分支继续尝试。这样最终表达式所有的可能途径知道最终的成功或失败。回溯常和备用状态联系起来分析,那就来说一下,什么叫备用状态。            

       按我的理解是:大家应该都知道迷宫,走迷宫最常用的方法就是做记号,此刻我们假如用石头做记号,每当我们遇到十字路口是,我们就丢下一块石头,来标记我们所选择的路口,但我们发现不此路不通时,我们就进行回溯,回退到离我们最近的哪一个十字路口,然后重新选择我们的方向,这种方法是我们最常用的。我要说的是,正则表达式中的备用状态就是那个我们在每个十字路口的石头。其实我觉得这就算递归中的回溯,要是你理解了递归,这个应该不难理解。NFA中的LIFO(后进先出)的原则我觉就是那个迷宫此路不通的记号石头就是我们最好的LIFO例子。               整个NFA中的优化其实就是这个回溯原则和greedy原则(上一篇中讲到的),进行合理的优化和匹配。      

     三、忽略优先量词(*?+???{minmax}?、)              

     针对NFAgreedy原则,正则表达式有了忽略优先量词,这很大程度上解决了,很多由于greedy原则带来的麻烦和效率的问题。她只会匹配符合要求的最小的选择。

    如: 若用 <B>.*</B> 来匹配<B>I</B>and<B>you</B>are chinese,we are very happy to be chinese.    结果会是:<B>I</B>and<B>you</B>   但用<B>.*?</B>匹配 则结果是:<B>I</B>      

     四:固化分组:(?>)    使用固化分组,这会让引擎不记住备用状态,这又是能很好的提高正则表达式的效率。这也是优化正则表达式的常用方式。如下面的例子:用 i.*! 来匹配 iHello!   在没有用固化分组时,是可以匹配成功的。  若用固化分组,i(?>.*)! 来匹配 iHello! 则匹配不成功,由于*是贪婪的她会一直匹配到最后,又由于固化分组了.*,她不会再吐出字符让后面的!来匹配。所以匹配失败。 

  • 相关阅读:
    剑指offer:二进制中1的个数
    剑指offer:反转链表
    剑指offer:链表中倒数第k个结点
    剑指offer:调整数组顺序使奇数位于偶数前面
    剑指offer:矩形覆盖
    剑指offer:变态跳台阶
    剑指offer :跳台阶
    剑指offer:旋转数组的最小数字
    剑指offer:用两个栈实现队列
    剑指offer:重建二叉树
  • 原文地址:https://www.cnblogs.com/dongqinglove/p/3543730.html
Copyright © 2011-2022 走看看