浅析正则表达式—(原理篇)

发布时间:2026/7/3 21:16:34
浅析正则表达式—(原理篇) 其实这篇文章很久之前就应该发出来由于种种原因没有发出来如果这篇文章中有错误还请大家指出小弟并改正之没有学不会的东西只有不想学的东西只要功夫深铁杵磨成针我的至理名言吾生也有涯而知也无涯以有涯随无涯殆矣。我们只要坚持将其看完相信大家的正则表达式会有一个提升空间本文属于.NET正则表达式里面的内容由于不同语言正则表达式有所不同。首先先讲解下正则表达式的基础知识1.字符串的组成对于字符串”123“而言包括三个字符四个位置。如下图所示2.占有字符和零宽度正则表达式匹配过程中如果子表达式匹配到东西而并非是一个位置并最终保存到匹配的结果当中。这样的就称为占有字符而只匹配一个位置或者是匹配的内容并不保存到匹配结果中这种就称作零宽度后续会讲到的零宽度断言等。占有字符是互斥的零宽度是非互斥的。也就是一个字符同一时间只能由一个子表达式匹配而一个位置却可以同时由多个零宽度的子表达式匹配。3.控制权和传动正则表达式由左到右依次进行匹配通常情况下是由一个表达式取得控制权从字符串的的某个位置进行匹配一个子表达式开始尝试匹配的位置是从前一子表达匹配成功的结束位置开始的例如表达式一表达式二意思就是表达式一匹配完成后才能匹配表达式二而匹配表达式二的位置是从表达式一的位置匹配结束后的位置开始。如果表达式一是零宽度那表达式一匹配完成后表达式二匹配的位置还是原来表达式以匹配的位置。也就是说它匹配开始和结束的位置是同一个。举一个简单的例子进行说明正则表达式123源数据123讲解首先正则表达式是从最左侧开始进行匹配也就是位置0处进行匹配首先得到控制权的是正则表达式中的“1”而不是源数据中的“1”匹配源数据中的“1”匹配成功将源数据的“1”进行保存到匹配的结果当中这就表明它占有了一个字符接下来就将控制权传给正则表达式中的“2”匹配的位置变成了位置1匹配源数据中的“2”匹配成功将控制权又传动给了正则表达式的“3”这时候匹配的位置变成了位置2这时候就会将源数据中的“3”进行匹配。又有正则表达式“3”进行传动控制权发现已经到了正则表达式的末尾正则表达式结束。一、元字符限定符描述模式.匹配出换行符以外的任意字符\d*\.\d\w匹配字母数字或下划线或者汉字或者下划线be\s匹配任意空白符rai?n\d匹配数字,\d{3}\b匹配单词开始或结束它只是匹配一个位置\d{2,}^匹配字符串开始\d{3,5}$匹配字符串结束\d{3,5}二、转义字符如果你想要得到元字符本身的话需要使用“\”来取消这些元字符的特殊意义三、字符类首先字符类使用“[]”包起来的例如以下这个例子(大小写要区分)①[aeiou]则表示匹配任意一个英文元音字母(这个仅仅是匹配一个也就是说你如果匹配了a这个整个正则表达式就已经结束了这里面的逻辑表示的是“或”的意思)再看这个例子[.!?]表示匹配.或者?或者②[a-zA-Z0-9]这个正则表达式表示的是匹配a到z的任意一个小写字母或者是A到Z的任意一个字母或者是数字0到9任意一个.四重复MSDN上称作是限定符代码/语法说明*重复0次或多次重复一次或多次?重复零次或1次{n}重复n次{n,}重复至少n次{n,m}重复至少n次但不多于m次五分支条件其实正则表达式中的分支条件就指的是有几种规则用“|”把不同的规则分开来看下例子①0\d{2}-\d{8}|0\d{3}-\d{7}:匹配两种以连字号分隔的电话号码一种是三位区号8位本地号例如010-12345678另外一种规则则是4位区号7位本地号例 如0315-8834524②\d{5}-\d{4}|\d{5}:需要注意的是使用分支条件是一定要注意分支条件的顺序如果改成\d{5}|\d{5}-\d{4}这个样子的话那么只会匹配五位数字而不会匹配后面的四位数字例如我们利用第二个匹配12345-1234它只会匹配12345原因是正则表达式是从左到右依次匹配如果满足了某个分支的话它就不会再管其他分支了六分组你可以使用小括号来指定字表达式①(\d{1,3}){3}\d{3}:这个正则表达式的意思就是把我们分组的小括号里面的东西重复三次也就是说我们至少匹配3个最多匹配9个数字后面再加上三个数字我们可以看图最后一个是1234567891 123也就是说前面是十个数字按照我们的常理来分析的话就应该匹配应该最多的是9个所以匹配之后的数到2就匹配成功了。OK我们讲到分组不知道你们对上面这幅图有没有什么想法对没错就是为啥还有0,1之分呢想知道答案跟我继续看下去保证你有意外收获哦也许大家会问为什么这里的写的1里面匹配的是这些数字我们稍后我们会为你解析这是为什么会是这些数字七反义字符代码/语法说明\W匹配任意一个不是字母或数字下划线或汉字的字符\S匹配任意一个不是空白符的字符\D匹配不是数字的字符\B匹配不是单词开头或者结尾的位置[^X]匹配除了X以外的任意字符[^aeiou]匹配除了aeiou这几个字母以外的任意字符八反向引用使用小括号指定一个子表达式后匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下每个分组会自动拥有一个组号规则是从左向右以分组的左括号为标志第一个出现的分组的组号为1第二个为2以此类推。但是其实分组号不是这么简单•分组0对应整个正则表达式•实际上组号分配过程是要从左向右扫描两遍的第一遍只给未命名组分配第二遍只给命名组分配因此所有命名组的组号都大于未命名的组号•你可以使用(?:exp)这样的语法来剥夺一个分组对组号分配的参与权通过上面三条讲述我们可以清楚地知道分组的方式是怎样的其实意思就是首先我们先对没有为组进行命名的组进行分配组号从左到右依次次分配然后再对分配组号的组进行分配组号使用(?组名)方式显示分配组名称如果你想剥夺某一个组的组号可以采用(?:exp)这种方式进行剥夺也就是不给他分配组号可以理解为跳过此组。看一下例子正则表达式(?work3)(1)(2)(?SmallDing565)匹配文本312565匹配结果表明首先0号组的是匹配的整个表达式匹配1号组名的则是1匹配2号组的是2匹配3号组的就是命名为work组名的3匹配4号组的则是匹配命名为SmallDing组名的565显然可以看到分配组号就是按照以上的规则来分配。说到了反向引用我们来看一下反向引用是什么概念我们前面已经详细讲解了组号的分配那么反向引用则用于重复搜索前面某个分组匹配的文本例如\1代表分组1匹配的文本请看下面的例子正则表达式(1)(2)(3)\2则表示匹配123且在此匹配组号为2的内容也就是再次匹配2匹配文本:1232匹配结果如下图所示而至于想知道怎样取消分组号那就跟着我的脚步走来看看下面的内容吧正则表达式(?work333)(?smallDing222)(?:\d{3})该正则表达式代表的是显示为匹配333的组分配组名为work显示为匹配222结果的组分配组名为222但是如果匹配3位数字这个组已经取消了组号所以该组号是没有的也就是整个正则表达式是第一个组号为0首先将所有未命名的组进行分配组号而只有一个(?:\d{3})这个没有分配组名但是它却将组号进行取消了所以组号不会给它分配。源文本为333222123匹配结果为如下图所示:那现在我们就来讲一下零宽断言和负零宽断言常见的几种分组方法分类代表/语法说明捕获exp匹配exp并捕获文本到自动命名的组里(?nameexp)匹配exp并捕获文本到名称为name的组里也可以写成(?’name’exp)(?:exp)匹配exp不捕获匹配文本也不给分组分配组号断言(?exp)匹配exp前面位置但是不匹配exp(?exp)匹配exp后面位置但是不匹配exp(?!exp)匹配后面的不是exp的位置但是不匹配exp(?!exp)匹配前面不是exp的位置但是不匹配exp注释(?#comment)注释零宽度断言1.(?exp):也叫零宽度正预测先行断言它匹配自身出现的位置后面能匹配表达式exp例如\b\w(?ing\b)则这个正则表达式就是匹配一ing结尾的单词但是不包含ing这个零宽度正预测先行断言可以这样理解我们就以上面的正则表达式作为例来进行讲解首先我们肯定是匹配源文本为doing它会先匹配d的时候它会瞻仰一下后面跟的是不是ing如果不是就会继续往下走匹配到第二个字符o它会预测或瞻仰下后面是不是ing如果是整个表达式就结束了并且不匹配ing。而这个可以总结一句话就是匹配exp前面的东西2.(?exp):也叫零宽度正回顾断言它匹配自身出现位置的前面匹配表达式exp这句话听着很绕口其实零宽度正回顾断言中解释说是自身出现位置这个自身出现位置是表示它匹配的文本就比如说(?Ding)\d{3}这个正则表达式这里的自身出现的位置仅仅是从开始匹配文本的时候也就是\d{3}也就是主动权在这个\d{3}的时候才是自身匹配的位置。举例说明源文本比如匹配Din123按照我们的常理理解的是数字123是自身匹配的位置但是前面不是Ding所以匹配不成功我们可以讲这个表达式理解为就是以exp为开始的正则表达式但是不包含exp意思就是匹配exp后面的东西。负向零宽断言可以和上面的进行对比来学哦这个表达式的是否定的1.(?!exp):也叫零宽度负预测先行断言断言此位置的后面跟的不能匹配表达式exp例如\d{3}(?!123):正则表达式的含义表达了前面匹配的是三个数字匹配的位置就是当前匹配的这三个数字后面跟的不能是123。2.(?!exp):零宽度负回顾断言断言此位置前面跟的不是exp的位置。九平衡组接下来我来讲一下平衡租的原理在上面我们做下了铺垫也就是说我们在第六节的时候提出来了一系列问题是不是感觉一头雾水没关系的到了这一节终于守得云开见月明了听过本章节的学习我相信你们会对上面的问题进行一个详细合理的回答OKCome On Baby懂你们迫不及待心情一定会说你咋这多废话呢好闲话少说继续....说到平衡组有些人就会想到分组没错他们之间是有联系的也就是我们前面所讲的分配组号的问题那下面呢我们先引出语法详细见下表语法说明(?’group’)把捕获的内容命名为group并压入堆栈(?’-group’)从堆栈上弹出最后压入堆栈名为group的捕获内容如果堆栈为空则本组匹配失败(?(group)yes|no)如果堆栈上存在名为group的捕获内容的话继续匹配yes部分的表达式否则匹配no的表达式(?!)零宽度负先行断言由于没有后缀表达式试图匹配总是失败也许大家看到这些语法都不知道是什么概念也不知道这个平衡组到底用在什么地方合适接下来我们我们就来说一个场景分析它用在什么位置比较合适有时我们需要匹配像( 100 * ( 50 15 ) )这样的可嵌套的层次性结构这时简单地使用\(.\)则只会匹配到最左边的左括号和最右边的右括号之间的内容(这里我们讨论的是贪婪模式懒惰模式也有下面的问题)。假如原来的字符串里的左括号和右括号出现的次数不相等比如( 5 / ( 3 2 ) ) )那我们的匹配结果里两者的个数也不会相等。有没有办法在这样的字符串里匹配到最长的配对的括号之间的内容呢为了避免(和\(把你的大脑彻底搞糊涂我们还是用尖括号代替圆括号吧。现在我们的问题变成了如何把xx aa bbb bbb aa yy这样的字符串里最长的配对的尖括号内的内容捕获出来接下来我们对这些语法进行分析怎么样一个平衡法大家都见过第一个语法语法的内容讲解的就是为一个组分配组名这里我们为什么还强调一下分配组名的问题么前面不是提到过这些问题了么那现在让我们解析一下平衡法以及用这些语法去构建一个平衡。我们先以一个例子开始正则表达式(?Group123)(?Group456)看这个正则表达式你会发现一些问题恩怎么给两个组分配了一个组名这样返回的Group组名获取的到底是个什么东东呢大家来猜一下匹配文本123456会是个什么结果先看一下测试结果我们可以看到0组当人不让的是整个表达式的而Group组里面获取的是456而不是123这是为什么呢那么我们就来分析一下他的原理一张图搞懂原理OK我们来讲一下组其实内部是一个堆栈也就是我们分别往组名为Group的堆栈中放入了两个内容第一个压入栈的是123而第二压入栈的是456Group组获取的文本是堆栈的top也就是栈顶的数据所以Group获取的数据是456而不是123那么有些人说了我不想要456我就想要123怎么实现这样也好办啊我们就弹出栈顶数据不就行了么看下面的实例(?Group123)(?Group456)(?’-Group’)这里的表达式(?’-Group’)就是压出堆栈栈顶的数据也就是如下图所示的那么现在栈顶的数据就是123了那么我们就来看一下匹配的结果是不是我们想的这样那么我们就可以想到分组名的是这样没有分组名的组也是这样的匹配原理那么我们回到第六章就可以将答案找出来为什么这个组里的数据会是这个了剩下还有(?(group)yes|no)深入讲解下这个表达式是什么意思我们前面已经讲到了分组是一个堆栈可以压入和弹出但是再弹出的时候我们不知道它有没有弹完用什么办法来可以检测它是不是已经到了栈底了呢那么用这个正则表达式就可以检测到它说的意思就是如果我们已经将数据全部都弹出去了就会执行一个表达式在No的位置“|”表示分割两种不同情况如果还存咱数据就说明还没有到栈底就会执行yes的表达式。那么我们就开始举例说明正则表达式(?’group’123)(?’group’456)(?’-group’)(?(group)1|2):这个表达式含义就是如果堆栈中还有数据就匹配1否则就匹配2看下面测试结果表明堆栈中还有数据。