正则表达式
正则表达式:是强大、便捷和高效的文本处理工具。
正则表达式如同一门编程语言的通用模式表示法。赋予使用者描述和分析文本的能力,能够添加、删除、分离、叠加、插入和修整各种类型的文本和数据。
完整的正则表达式由两种字符组成:特殊字符(称为元字符)和普通文本字符。
「^」:代表一行的开始。如「^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.回溯进行时,应该选取哪个保存的状态?
距离当前最近储存的选项就是强制回溯时返回的。使用的是先进后出的原则。
原文链接:https://www.f2er.com/regex/360869.html