正则(高级)(转)

深刻理解正则表达式高级教程 Featured

做者:  Zjmainstay

本文是一篇正则表达式高级教程,主要经过对正则表达式几个概念的介绍,深刻探讨正则表达式高级功能,以期达到通俗化解释正则表达式高深概念的目的。javascript

 

 

前面已经写过一篇文章《我眼里的正则表达式(入门)》介绍过正则表达式的基础和基本套路正则三段论:定锚点,去噪点,取数据了,接下来这篇文章,补充一点相对高级的概念:php

1. 概念一:按单字符匹配
    2. 概念二:匹配优先和不匹配优先
    3. 概念三:贪婪模式与非贪婪模式
    4. 概念四:环视(断言)
    5. 概念五:平衡组
    6. 概念六:模式修饰符
    7. 附:正则三段论应用
 

概念一:按单字符匹配

正则里面的数据都是按照单个字符来进行匹配的,这个经过数值区间的例子最容易体现出来,好比,示例一:java

我要匹配0-15的数值区间,用正则来写的话,即是[0-9]|1[0-5],这里,即是把0-9这种单字符的状况,和10-15这种多字符的状况拆分开了,使用分支|来区分开,表示要么是0-9,要么是10-15。 
上面是两位数值的状况,如今延伸至1-65535,我我的的处理思想是从大到小,一块块分解:正则表达式

 
  1. 1. 65530-65535 ==> 6553[0-5] 末位区间0-5
  2. 2. 65500-65529 ==> 655[0-2][0-9] 第四位区间0-2,末位区间0-9
  3. 3. 65000-65499 ==> 65[0-4][0-9]{2} 第三位区间0-4,后两位0-9
  4. 4. 60000-64999 ==> 6[0-4][0-9]{3} 第二位区间0-4,后三位0-9
  5. 5. 10000-59999 ==> [1-5][0-9]{4} 第一位区间1-5,后四位0-9
  6. 6. 1-9999 ==> [1-9][0-9]{0,3} 第一位只能是1-9,后三位无关紧要

最后组合起来: 
(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3}) 
便获得1-65535匹配正则。数据结构

根据数据处理需求,能够在正则先后加上^$,以匹配整个数据串,或者先后加入\b,把它当作单词边界处理。没有限定字符的边界每每是js正则判断中常见的错误之一。ide

 

概念二:匹配优先和不匹配优先

匹配优先和不匹配优先从字面理解也是比较容易的,所谓匹配优先,就是,能匹配我就先匹配;不匹配优先就是,能不匹配我就先不匹配,这段匹配先跳过,先看看后面的匹配能不能经过。工具

 

概念三:贪婪模式与非贪婪模式

正则的贪婪模式和非贪婪模式是一个比较容易混淆的概念,不过,咱们能够这么理解,一我的很贪婪,因此他会能拿多少拿多少,换过来,那就是贪婪模式下的正则表达式,能匹配多少就匹配多少,尽量最多。而非贪婪模式,则是能不匹配就不匹配,尽量最少。性能

下面举个例子,示例二:学习

需求:匹配1后面跟任意个0
源串:10001
使用贪婪模式:10*       结果:1000 和 1
使用非贪婪模式:10*?    结果:1 和 1

首先,*是匹配0个或多个的意思。测试

 
  1. 贪婪模式下,它表示,首先匹配一个1,而后匹配1后面的0,最多能够匹配30,所以获得1000,而后第二次又匹配到一个1,可是后面没有0,所以获得1
 
  1. 非贪婪模式下,它表示,首先匹配一个1,而后1后面的0,能不匹配就不匹配了,因此,它每次都只是匹配了一个1

看到这里,也许,有些人以为,哎呀,我懂了! 
那么,下来咱们改改,看看你是否是真懂了。

示例三:

需求:匹配1后面跟任意个0,再跟一个1
源串:10001
使用贪婪模式:10*1       结果:10001
使用非贪婪模式:10*?1    结果:10001

为何这两次的结果同样了?

由于,正则表达式要判断完这整个正则才算成功,这种状况下,

 
  1. 贪婪模式,首先匹配一个1,而后匹配1后面尽量多的0,发现3个,再匹配0后面的一个1,正则表达式匹配完,完成匹配,获得10001
 
  1. 非贪婪模式,首先匹配一个1,而后,0*?是非贪婪模式,它不想匹配了(非贪婪模式不匹配优先),看看后面是否是1了?而后发现哎妈呀,后面是个0啊,而后它回头,不能再偷懒了,用0*?匹配一个0吧,而后匹配到10,又不想匹配了,看看后面有没有1了,仍是没有,又回去用0*?匹配掉一个0,获得100,继续偷懒,可是发现后面还不是1,而后又用0*?匹配获得1000,最后,发现真不容易啊,终于看到1了,匹配获得10001,正则表达式匹配完,完成匹配。

看到这里,是否是懂了? 
那究竟哪一个模式好呢?

何时使用贪婪模式,何时使用非贪婪模式,哪一个性能好,哪一个性能很差,不能一律而论,要根据状况分析。 
下面我举个例子: 
源码:

 
  1. <a href="http://www.zjmainstay.cn/my-regexp" target="_blank" title="我眼里的正则表达式(入门)">我眼里的正则表达式(入门)</a>
  2. <a title="我眼里的正则表达式(入门)" href="http://www.zjmainstay.cn/my-regexp" target="_blank">我眼里的正则表达式(入门)</a>

正则1:<a [^>]*?href="([^"]*?)"[^>]*?>([^<]*?)</a>(238次) 
正则2:<a [^>]*?href="([^"]*)"[^>]*>([^<]*)</a>(65次) 
正则3:<a [^>]*href="([^"]*)"[^>]*>([^<]*)</a>(136次) 
附:执行次数的获取请下载正则表达式测试工具:RegexBuddy 4.1.0-正则测试工具.rar,使用里面的Debug功能。

正则1是通用写法,正则2是在肯定字符不会溢出的状况下消除非贪婪模式,正则3是证实并非所有消除非贪婪模式就是最优。

所以,关于贪婪模式好仍是非贪婪模式好的讨论,只能说根据需求而定,不过,在平时的时候用,通常使用非贪婪模式较多,由于贪婪模式常常会因为元字符范围限制不严谨而致使匹配越界,获得非预期结果。

在肯定的数据结构里,能够尝试使用[^>]*>这样的排除字符贪婪模式替换非贪婪模式,提高匹配的效率。注意,贪婪部分([^>]*)的匹配,最好不要越过其后面的字符(>),不然会致使贪婪模式下的回溯,如正则3,[^>]*的匹配越过了href,一直匹配到>为止,而这时候再匹配href,会匹配不到而致使屡次回溯处理,直到回溯到href前的位置,后面才继续了下去。

另外,须要注意一点,不管使用贪婪模式仍是非贪婪模式,在不一样语言须要注意回溯次数和嵌套次数的限制,好比在PHP中,pcre.backtrack_limit=100000pcre.recursion_limit=100000

 

概念四:环视(断言/零宽断言)

环视,在不一样的地方又称之为零宽断言,简称断言。 
用一句通俗的话解释: 
环视,就是先从全局环顾一遍正则,(而后判定结果,)再作进一步匹配处理。 
断言,就是先从全局环顾一遍正则,而后判定结果,再作进一步匹配处理。

两个虽然字面不同,意思倒是同一个,都是作全局观望,再作进一步处理。

环视的做用至关于对其所在位置加了一个附加条件,只有知足这个条件,环视子表达式才能匹配成功。

环视主要有如下4个用法: 
(?<=exp) 匹配前面是exp的数据 
(?<!exp) 匹配前面不是exp的数据 
(?=exp) 匹配后面是exp的数据 
(?!exp) 匹配后面不是exp的数据

示例四: 
(?<=B)AAA 匹配前面是B的数据,即BAAA匹配,而CAAA不匹配 
(?<!B)AAA 匹配前面不是B的数据,即CAAA匹配,而BAAA不匹配 
AAA(?=B) 匹配后面是B的数据,即AAAB匹配,而AAAC不匹配 
AAA(?!B) 匹配后面不是B的数据,即AAAC能匹配,而AAAB不能匹配

另外,还会看到(?!B)[A-Z]这种写法,其实它是[A-Z]范围里,排除B的意思,前置的(?!B)只是对后面数据的一个限定,从而达到过滤匹配的效果。

所以,环视作排除处理是比较实用的,好比,示例五:

需求:字母、数字组合,不区分大小写,不能纯数字或者纯字母,6-16个字符。
通用正则:^[a-z0-9]{6,16}$    字母数字组合,6-16个字符
    排除纯字母:(?!^[a-z]+$)
排除纯数字:(?!^[0-9]+$)
    组合起来:(?!^[a-z]+$)(?!^[0-9]+$)^[a-z0-9]{6,16}$

注意,环视部分是不占宽度的,因此有零宽断言的叫法。 
所谓不占宽度,能够分红两部分理解: 
一、环视的匹配结果不归入数据结果 
二、环视它匹配过的地方,下次还能用它继续匹配。

若是不是环视,则匹配过的地方,不能再匹配第二次了。

上面示例四体现了:环视的匹配结果不归入数据结果,它的结果:

 
  1. (?<=B)AAA 源串:BAAA 结果:AAA
  2. (?<!B)AAA 源串:CAAA 结果:AAA
  3. AAA(?=B) 源串:AAAB 结果:AAA
  4. AAA(?!B) 源串:AAAC 结果:AAA

而示例五体现了:环视它匹配过的地方,下次还能用它继续匹配 
由于,整个匹配过程当中,正则表达式一共走了3次字符串匹配,第一次匹配不所有是字母,第二次匹配不所有是数字,第三次匹配所有是字母数字组合,6-16个字符。

 
  1. 扩展部分:
  2. `[A-Z](?<=B)` [A-Z]范围等于B
  3. `[A-Z](?<!B)` [A-Z]范围排除B
  4. `(?!B)[A-Z]` [A-Z]范围排除B

附: js不支持(?<=exp) 和 (?<!exp) 语法

 

概念五:平衡组

平衡组并非全部程序语言都支持,而我本人使用的PHP语言就不支持,因此平时接触也是比较少的。

平衡组主要用到下面四个语法:

 
  1. (?'group') 把捕获的内容命名为group,并压入堆栈(Stack)
  2. (?'-group') 从堆栈上弹出最后压入堆栈的名为group的捕获内容,若是堆栈原本为空,则本分组的匹配失败
  3. (?(group)yes|no) 若是堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,不然继续匹配no部分
  4. (?!) 零宽负向先行断言,因为没有后缀表达式,如没有(?!B)的B,试图匹配老是失败

在PHP中是支持(?(group)yes|no)语法的,这里的group是分组编号,即子模式编号,如(A)?(?(1)yes|no) ,匹配Ayes 和 no

下面这里引用《正则表达式30分钟入门教程#平衡组》关于<>配对匹配的例子,展现平衡组用法,

 
  1. < #最外层的左括号
  2. [^<>]* #最外层的左括号后面的不是括号的内容
  3. (
  4. (
  5. (?'Open'<) #碰到了左括号,在黑板上写一个"Open"
  6. [^<>]* #匹配左括号后面的不是括号的内容
  7. )+
  8. (
  9. (?'-Open'>) #碰到了右括号,擦掉一个"Open"
  10. [^<>]* #匹配右括号后面不是括号的内容
  11. )+
  12. )*
  13. (?(Open)(?!)) #在遇到最外层的右括号时,判断黑板上还有没有没擦掉的"Open";若是还有,则匹配失败
  14. > #最外层的右括号
  15. 平衡组的一个最多见的应用就是匹配HTML,下面这个例子能够匹配嵌套的<div>标签:
  16. <div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>
 

概念六:模式修饰符

模式修饰符在许多程序语言中都支持的,好比最多见的是i,不区分大小写,如javascript里的/[a-z0-9]/i,表示匹配字母数字,不区分大小写。

本人在写php正则时经常使用的模式修饰符主要有is,如: 
$pattern = '#[a-z0-9]+#is';

模式修饰符s的做用主要是的.可以匹配换行,在处理换行数据时,一般会用到。

在PHP中,模式修饰符有两种用法,一种是上面的,在分隔符后面的模式修饰符,它的做用范围是全局;另外一种是在正则表达式中间的。 
例如:

 
  1. 正则:/((?i)[A-Z]+)c/
  2. 测试字符:abcABC
  3. 匹配:abc
  4. 说明:局部(ab)的大小写被忽略了,(?i)的做用范围在分组1

若是把正则改为:/([A-Z]+)c/i,则匹配结果将是:abcABC 
示例地址:PHP正则表达式中间的模式修饰符

关于PHP模式修饰符的讲解,请查看PHP手册中的《PHP模式修饰符》。

 

七:正则三段论应用

我眼里的正则表达式(入门)》 和 本文《深刻正则表达式应用》几乎倾尽本人正则学习所有思想,可是不少读者反馈,看晕了!看到如此点评,实属无奈,所以,有必要追加本节,来个总体统筹运用,但愿能让你们犹如拨云见月,洞悉其中的精义。

正则三段论:定锚点,去噪点,取数据

两篇文章中,最重要的部分当属正则三段论:定锚点,去噪点,取数据,它是整个正则处理过程当中的灵魂,它贯穿整个正则撰写过程。

下面举例说明它的思想,示例六:

 
  1. 源数据:标题:深刻正则表达式应用,做者:Zjmainstay
  2. 需求:匹配做者名字

我要从源数据取到Zjmainstay这个做者名,那么,在这里,做者:就是咱们所说的锚点,由于在上面这段数据中它可以惟必定位到咱们的数据Zjmainstay(就在它后面),所以,咱们获得

(1) 定锚点:做者:

而在这里,咱们不须要关心标题什么的,所以,标题:深刻正则表达式应用,就是咱们的噪点,所以,咱们获得

(2) 去噪点

最后,咱们肯定做者:后面就是咱们的数据,这个数据能够是任意字符,所以,咱们获得正则: 
做者:(.*)

而噪点部分,由于不会对数据取值形成干扰,直接去掉,不须要引入正则中。

下面再举一个噪点干扰的例子,示例七:

 
  1. 源数据: <a href="http://www.zjmainstay.cn/my-regexp" class="demo8" title="正则三段论应用举例">正则表达式入门教程</a>
  2. 需求:提取连接和标题,还有a标签的文字

看到这个源数据和需求,咱们必须定位好锚点,主要有: 
1. <a //必须是a标签 
2. href=" 和 " //href=""的内容获得连接 
3. title=" 和 " //title=""的内容获得标题 
4. > 和 </a> //>和</a>的内容获得标签文字

而后,其余的都是噪点,使用.*?替代,须要提取的数据部分使用括号获取子模式,获得分组数据,所以获得正则: 
<a href="(.*?)".*?title="(.*?)">(.*?)</a>

看到这里,也许有朋友以为,我仍是不会写,那么,再来一个更简单的构建方法,细化步骤,从源串逐步获得正则,示例八:

 
  1. 1. 直接拷贝源串,特殊字符处理转义(本例没特殊字符)
  2. <a href="http://www.zjmainstay.cn/my-regexp" class="demo8" title="正则三段论应用举例">正则表达式入门教程</a>
  3. 2. 从左到右,一段段转化
  4. 2.1 <a href="(.*?)" class="demo8" title="正则三段论应用举例">正则表达式入门教程</a>
  5. 2.2 <a href="(.*?)".*?title="正则三段论应用举例">正则表达式入门教程</a>
  6. 2.3 <a href="(.*?)".*?title="(.*?)">正则表达式入门教程</a>
  7. 2.4 <a href="(.*?)".*?title="(.*?)">(.*?)</a>
  8. 3. 获得最终的正则
  9. <a href="(.*?)".*?title="(.*?)">(.*?)</a>

至此,正则三段论的基本思想已经展现完毕,你们还有什么不解请评论留言,本人看到会第一时间给予回复。

熟悉正则三段论处理思想,剩下的即是基本语义的熟练程度了,这个经过多用多练能够达到熟能生巧的境界。

最后留下一句至尊提醒:.是万能字符,你们看着用,遇到换行使用[\s\S]替换.便可。

转载请附带本文原文地址: 深刻理解正则表达式高级教程,首发自  Zjmainstay学习笔记
相关文章
相关标签/搜索