正则表达式(整理)

正则表达式

正则表达式:是强大、便捷和高效的文本处理工具。

正则表达式如同一门编程语言的通用模式表示法。赋予使用者描述和分析文本的能力,能够添加、删除、分离、叠加、插入和修整各种类型的文本和数据。

 

完整的正则表达式由两种字符组成:特殊字符(称为元字符)和普通文本字符。

 

「^」:代表一行的开始。如「^cat」匹配以c作为一行的第一个字符,紧接一个a,再紧接一个t的文本。

 

「$」:代表一行的结束。「cat$」与「^cat」同理。

 

「^」和「$」通常匹配的不是逻辑行的开头和结尾,而是整个字符串的开头和结尾。

 

字符组

「[…]」:字符组,它容许使用者列出在某处期望匹配的字符。如「gr[ea]y」匹配grey或gray。

 

在字符组内部,字符组元字符‘-’表示一个范围。如「[0-9]」匹配数字。

只有在字符组内部并且不在字符组的开头,连字符才是元字符,否则它就是一个普通的连字符号。同样,问号和点号等通常被当作元字符处理,但在字符组中则不是如此,如「[0-9A-Z_!.?]」里面真正的元字符只有那两个连字符。

 

排除型字符组

用「^…」取代「…」,这个字符组就会匹配任何未列出的字符。如:「[^1-6]」匹配除了1到6以外的任何字符。

在字符组外部^表示一个行锚点,但在字符组内部而且必须是在字符组的第一个方括号之后,它就是一个元字符。

 

排除型字符组表示“匹配一个未列出的字符”,而不是“不要匹配列出的字符”

 

用点号匹配任意字符

元字符「.」是用来匹配任意字符的字符组的简便写法。如我们需要匹配03/19/76、03-19-76、或03.19.76。可以用「03[-/.]19[-./]76」来匹配,也可以尝试「03.19.76」来匹配。但用「03.19.76」来匹配可能出现不期望的结果,如03419076也可以匹配到。

通常在支持Unicode的系统中,「.」不能匹配换行符。

 

多选结构

「|」是一个简捷的元字符,它的意思是“或”。依靠它,我们可以把不同的子表达式组合成一个总表达式,而这个总表达式可以匹配任意的子表达式。

如「Bob」和「Rebert」是两个表达式,但「Bob|Rebert」就是能够同时匹配其中任意一个的正则表达式,在这样组合中,子表达式称为“多选分支”。

 

前面所讲的「gr[ea]y」也可以写做「grey|gray」或者「gr(e|a)y」,括号用来划定多选结构的范围。

 

多选结构的优先级很低,如「this and|or that」等价于「(this and)|(or that)」,而不是「this (and|or) that」。

 

多选结构将会从左到右测试每个条件,如果满足了某个多选条件,就不会去管其它的条件了。

 

一个字符组只能匹配目标文本中的单个字符,而每个多选结构可以匹配任意长度的文本。多选结构没有排除功能。

 

单词分界符

元字符序列「\<」和「\>」,可以使用它们来匹配单词分界的位置。如「\<cat\>」就可以匹配cat这个单词。

「<」和「>」本身不是元字符,只有当它们与斜杠结合起来的时候,整个序列才具有特殊意义。

注意:并不是所有的流派都支持单词分隔符。

 

可选项元素

元字符「?」代表可选项,把它加在一个字符的后面,就表示此处允许出现这个字符。

如需匹配color或colour则可以用「colou?r」来匹配。这里的「u?」元字符只作用于之前紧邻的元素。「?」也可以作用于括号,因为括号内的元素是一个整体。

 

 

其他量词:重复出现

「+」表示之前紧邻的元素出现一次或多次。

「*」表示之前紧邻的元素出现零次或多次。

问号、加号和星号这3个元字符统称为量词,因为它们限定了所作用元素的匹配次数。

 

 

规定重现次数的范围:区间

某些工具能够使用元字符序列来自定义重现次数的区间:「…{min,max}」,这称为区间量词。如「…{3,12}」允许的重现次数在3到12之间。如果只设置了一个数值,那么匹配的次数就等于这个值。

 

括号及反向应用

在许多流派的正则表达式中,括号能够记住它们包含的子表达式匹配的文本。

如在「\<([A-Za-z]+)  +\1\>」中「\1」就能记住括号中的匹配到的单词。当然在表达式中可以使用多个括号。在使用「\1」「\2」「\3」来表示第一、第二和第三组括号。

 

神奇的转义

反斜线称为“转义符”,它作用的元字符会失去特殊含义,成为普通字符。但在字符组内无效。

如果反斜线后紧跟的不是元字符,反斜线的意义就依程序的版本而定。

 

总结前面见到的元字符



请务必理解以下几点:

1.各个流派程序有差别,它们支持的元字符,以及这些元字符的确切含义通常都有差别。

2.使用括号的3个理由:限制多选结构、分组和捕获文本。

3.字符组的特殊性在于,关于元字符是完全独立于正则表达式语言“主体”的。

4.多选结构和字符组是截然不同的,它们的功能完全相同,只是在有限的情况下,它们的表现不同。

5.排除性字符组仍需要匹配一个字符,只是列出的字符都会排除,所以最终匹配的字符肯定不再列出的字符之内。

6.转义有2种情况:

(a)   反斜杠加上元字符,表示匹配元字符的所有普通字符。

(b)   反斜杠加上非元字符,组成一个由具体实现方式规定其意义的元字符序列。

7.由星号和问号限定的对象即使什么字符都不能匹配,但它们仍然会匹配成功。

 

 

非捕获型括号

一些正则表达式流派提供了「(?:…)」来表示只分组不捕获。这里的问号和表示“可选项”没有任何联系。

 

用「\s」匹配所有“空白”

「\s」表示所有的“空白字符”的字符组,其中包括空格符、制表符、换行符和回车符。

 

环视结构

大的数值,如果在其间加入逗号,会更容易看懂。如“123456789”和“123,456,789”。

环视结构不匹配任何字符,只匹配文本中的特定位置。这一点与「\b」、「^」和「$」相似,但环视更加通用。

 

肯定顺序环视(从左到右)查看文本。顺序环视用「(?=…)」来表示,如「(?=\d)」,它表示如果当前位置右边的字符是一个数字则匹配成功。「\d」表示匹配所有的数字字符,与「[0-9]」相同。

 

肯定逆序环视(从右向左)查看文本。逆序环视用「(?<=…)」来表示,如「(?<=\d)」,它表示如果当前位置左边的字符是一个数字则匹配成功。

 

环视在检查子表达式匹配的过程中,它们本身不会占用任何文本。如有这样一个字符串:“by Jeffery Friedl”,我们使用「(?=Jeffery)」,则匹配的是紧挨Jeffery前面的那个位置(也就是J前面的位置)。而使用「(?<=Jeffery)」,则匹配的是紧挨Jeffery后面的那个位置(也就是y后面的位置)。

 

现在就来解决大数值的问题,其思路是:找出左边有数字,右边数字的个数正好是3的倍数的位置,然后将其替换成‘,’即可。我们用「(?<=\d)(?=(?:\d\d\d)+$)」来找出上述的位置。

 

以上成功的条件是子表达式在这些位置能够匹配,那如果子表达式无法匹配呢?

正则表达式还提供了相应的否定顺序环视和否定逆序环视。见下图:

如果有“123456789 abc”这样一个字符串,我们使用上述解决大数值的表达式就不行了,但我们可以使用「(?<=\d)(?=(?:\d\d\d)+(?!\d))」就可以解决了。

 

在某些流派中,它们并不支持「\b」、「\<」和「\>」这样的单词分界符,而单词分界符的意思是:一侧是「\w」(包含大小写英文字母和数字,与「[a-zA-Z0-9]」相同),另一侧不是「\w」。我们就可以用「(?<!\w)(?=\w)」来替代「\<」,用「(?<=\w)(?!\w)」来替代「\>」,将两者结合起来「(?<!\w)(?=\w)|(?<=\w)(?!\w)」就可以替代「\b」。

 

忽略大小写的匹配

可以用「(?i)」来开启不区分大小写的匹配,用「(?-i)」来停用该匹配。也有的流派支持「(?i:…)」和「(?-i:…)」来启用或停用对括号内的子表达式进行不区分大小写匹配的功能。

 

文字文本范围

「\Q…\E」它会消除「\Q」到「\E」之间的所有元素的特殊含义(如果没有「\E」,就会一直作用到正则表达式的末端)。其间的所有字符都会被当成普通文字文本对待。如有一个字符串“C:\WINDOWS\”,我们用正则表达式「C:\WINDOWS\」来进行匹配,结果报错,因为「C:\WINDOWS\」不是一个合法的正则表达式,可以用「\QC:\WINDOWS\\E」来解决。

 

固化分组

固化分组「(?>…)」就是一旦括号内的子表达式匹配之后,匹配的内容就固定下来,在接下来的匹配过程中不会变化,除非整个固化分组的括号都被废弃。看下面的例子:

「i.*!」能够匹配“iHola!”,但如果「.*」在固化分组「i(?>.*)!」中就无法匹配。

在这两种情况下,「.*」首先会尽可能匹配更多的内容,但在后面的「!」会强迫「.*」释放之前匹配的某些内容(最后面的“i”),可「.*」在固化分组中,它永远也不会“交还”已经匹配的任何内容。

 

忽略优先量词

「*?」、「+?」、「??」、「{min,max}?」这些都是忽略优先量词。量词在通常情况下都是匹配优先的,匹配尽可能多的内容。相反,这些忽略优先的量词会匹配尽可能少的内容。

 

占有优先量词

「*+」、「++」、「?+」、「{min,max}+」这些都是占有优先量词。占有优先量词与普通优先量词一样,不过它们一旦匹配某些内容,就不会“交还”,类似与固化分组。

 

Java中的正则处理

Java中与正则表达式息息相关的两个类是位于java.util.regex包下的Pattern和Matcher。如下程序:

Pattern pattern = Pattern.compile("(regex)",Pattern.CASE_INSENSITIVE);// 1

      Matcher matcher = pattern.matcher("text");          // 2

      while (matcher.find())                              // 3

      {

         String s = matcher.group(1);                     // 4

         System.out.println(s);

      }

1:检查正则表达式,将它编译为不区分大小写匹配的形式,得到一个Pattern对象。

2:将它与欲匹配的文本关联起来,得到一个Matcher对象。

3:应用这个正则表达式,检查是否存在匹配,并返回结果。

4:如果存在匹配,提取第一个捕获括号内的文本。

 

java.util.regex包中的字符组能够进行完整的集合运算(并、减、交)。

 

OR(并集)容许用户以字符组方式在字符组内添加字符,如「[abcxyz]」       也可以表示为「[[abc][xyz]]」、「[abc[xyz]]」或「[abc]xyz」等。OR用来把多个集合合并为新的集合。

AND(交集)对两个集合进行“与”运算,只保留同时属于两个字符组的字符。如「a-z&&[aeiou]」匹配的就是小写的元音字母。

 

表达式「a-z&&[^aeiou]」匹配小写非元音字母。

 

 

 

 

 

 

 

匹配原理

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

2.标准的匹配量词是匹配优先的。

 

优先选择最左端(最靠前头)的匹配结果:匹配先从需要查找的字符串的起始位置尝试匹配,如果在当前位置测试了所有的可能之后不能找到匹配结果,就从字符串的第二个字符之前的位置开始重新尝试匹配,依此类推,直到字符串的最后一个字符还没有找到匹配结果,报告匹配失败。如果能够匹配成功,引擎停下来,报告匹配结果。

 

标准的匹配量词是匹配优先的:标准量词的匹配结果并非所有可能中最长的,但它们总是尝试匹配尽可能多的字符,直到匹配上限为止。

 

传动装置的主要功能:驱动

如果引擎不能在字符串开始位置找到匹配结果,传动装置就会推动引擎,从字符串的下一个位置开始尝试,如此继续。

 

正则引擎分类

正则引擎可以粗略分为3类:DFA引擎,传统型NFA和POSIX NFA。

 

引擎的构造

正则引擎中的组件分为文字字符、量词、字符组、括号等等。这些组件的组合方式决定了引擎的特性。

文本文字:尝试匹配需要考虑的是这个字符与当前尝试的字符相同吗?

字符组、点号。Unicode属性及其他:无论字符组的长度是多少,它都只能匹配一个字符。

捕获型括号:用于捕获文本的括号(不是用于分组的括号)不会影响匹配的过程。

锚点:锚点分为简单锚点和复杂锚点。简单锚点(如「^」、「$」「\b」等)是检查目标字符串中的特定位置,而复杂锚点(如环视)能包含任意复杂的子表达式,可以任意复杂。

 

NFA引擎:表达式主导

如果这种尝试失败,引擎会尝试另一种可能,如此继续下去,直到匹配成功或是失败,表达式中的控制权在不同元素之间转换,我们称之为“表达式主导”。

 

DFA引擎:文本主导

与表达式主导的NFA不同,DFA引擎在扫描字符串时,会记录“当前有效”的所有匹配可能。引擎移动时,它会在当前处理的匹配可能中添加一个潜在的可能,接下来扫描的每个字符,都会更新当前的可能匹配序列,扫描的字符串中的每个字符都对引擎进行了控制,我们称这种方式为“文本主导”。

 

比较NFA和DFA

一般情况下,文本主导的DFA引擎要快些。正则表达式主导的NFA引擎,因为需要对同样的文本尝试不同的子表达式匹配,会浪费一些时间。

DFA引擎对目标文本中的每个字符只会检查一遍,对于一个已经匹配的字符,你无法知道它是否属于最终匹配,因为引擎同时记录了所有的匹配,这个字符只需要检测一次。

 

因为NFA是表达式主导的,那么调校好一个表达式能够带来许多收益,调校不好则会带来严重后果。

 

回溯

回溯是NFA引擎中最重要的性质,它会一次处理各个子表达式或组成元素,遇到需要在两个可能成功的选择时,它会选择其一,同时记住另一个,以备后面的需要。

回溯就像是在每个分岔口留下一些面包屑,如果走了死路,就原路返回,直到找到出路或走完所有的路。

 

回溯的两个要点:

1.面对众多选择时,哪个分支应当首先选择?

如果需要在“进行尝试”和“跳过尝试”之间选择,对于匹配优先量词,引擎会优先选择“进行尝试”,而对于忽略优先量词,会选择“跳过尝试”。

2.回溯进行时,应该选取哪个保存的状态?

距离当前最近储存的选项就是强制回溯时返回的。使用的是先进后出的原则。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值