原文地址:http://thihy.iteye.com/blog/1777065

本文是在学习正则表达式过程中整理的,虽然冠以教程,但实际上应该算是学习笔记。整篇文章需要对正则有一定的理解。。如果有啥写得不对的,或者写得不够清楚的,欢迎大家留言讨论。

概述

正则表达式(Regular Expression)是高效的、便捷的文本处理工具,能够快速查询符合某种规范的文本。

例如:[0-9]{3}可以匹配3位数字,[a-z]{3}则可以匹配3个小写字母。

目前正则表达式被众多工具所支持,比如egrepsedperlrubyJavaC#pythonTcl等,不同的工具下,正在表达式的范式可能会有略微的差别,执行引擎也可能不同。目前,正则引擎主要有:DFA, 传统型NFA, POSIX NFA, DFA/NFA混合。本文主要介绍Java正则表达式,它的引擎属于传统型NFA

Java正则支持Unicode,它在适当的时候,会使用java.lang.Character.codePointAt(CharSequence seq, int index)获取Code Point,而不是char

构造方法

Java中的正则表达式的构造方法可以看Patternjavadoc文档。

字符

对于可见字符,可以直接编写,这没啥难点。对于其它难以描述的字符,Java提供了一些表示方法。

字符缩略表示法

Java执行使用\x来代表特殊的含义,有:

\\

反斜线字符

\t

制表符 (\u0009)

\n

新行(换行)符 (\u000A)

\r

回车符 (\u000D)

\f

换页符 (\u000C)

\a

报警(bell)符 (\u0007)

\e

转义符 (\u001B)

\v

垂直制表符 (\u000B)

控制字符\cchar

Java可以使用\cchar匹配控制字符。其中char的值为64 ^ 控制字符,比如对于退格符(\b‘),ASCII码为8,char为64 ^ 8 = 72,也即H(HASCII码为72)。简单地,对于ASCII码小于64的控制字符,char为控制字符的ASCII码加上64

八进制表示:\0n, \0nn, \0mnn

Java中,八进制表示必须以\0开始(防止与反向引用混淆),这点可能与其他工具不同(某些工具有规则来区分反向引用和八进制)。\0后面的部分最多只能有3个数字,而n必须在区间[0,7]内,m必须在[0,3]内,所以最大表示\0377

十六进制表示:\xhh

十六进制后面必须是两个十六进制字符,也即h必须是0~9,a-fA-F\x00\xff都是合法的,但是\xa,\x0ab,\x1g都是不合法的。

Unicode转义:\uhhhh

Unicode转义的形式与十六进制类似,只是后面必须是四个六进制字符。

行结束符

行结束符是用来标记输入字符序列的行结尾,可能有一个或两个字符。在Java中,行结束符包括:

o 新行(换行)符 (\n)

o 后面紧跟新行符的回车符 (\r\n)

o 单独的回车符 (\r)

o 下一行字符 (\u0085)

o 行分隔符 (\u2028)

o 段落分隔符 (\u2029)

如果启用了UNIX_LINES模式,则新行符(\n)是惟一识别的行结束符。

只有启用DOTALL标志,正则表达式中的点号(.’)才会匹配行结束符。

默认情况下,正则表达式^$会忽略行结束符,仅分别与整个输入序列的开头和结尾匹配。如果激活 MULTILINE 模式,则 在输入的开头和行结束符之后(输入的结尾)才发生匹配。处于 MULTILINE 模式中时,仅在行结束符之前或输入序列的结尾处匹配。

字符类

字符类[…]

字符类的形式是[…],其内部可以是若干字符,也可以是一个字符范围。比如[a]表示匹配a字符,[a-z]表示匹配所有小写的英文字母。字符范围中的字符可以是Unicode字符,并且不要求是同一类的,也即[a-}]也是可以的,甚至是[a-]

字符类集合运算

字符类可以进行补集、并集(隐式)、交集和差集的运算。所有的集合运算都必须在字符类内部实现。

补集

如果字符类中以^开头,则表示是一个补集。比如[^a]表示匹配不是a的所有字符,这与[a^]是不同的,后者表示匹配a字符或^字符。既然是补集,那么需要明确全集是什么。由于Java是支持Unicode的,所以全集是所有的Unicode字符。也即[^a]可以匹配汉字字符。注:很多书籍上(包括JavaDoc)都没有谈到补集的概念,而是作为基本的字符类,但我觉得成为补集操作更加便于理解。

并集

可以在字符类中以字符类的方式来进行并集操作,比如对于[123456],可以表示为[123[456]][[123][456][[1][2][3][4][5][6]][[1[2]][3][4[5]6]]。并集操作时隐式的,没有特别的操作符,只需要按次序排在一起就OK了。

交集

交集操作可以保留两个字符类的共同部分,它要求两个字符类之间添加交集运算符&&。例如[[1-5]&&[3-9]]等价于[3-5]。通过环视功能可以模拟交集运算,比如(?=[1-5])[3-9]和[3-9](?<=[1-5])都等价于[3-5]

差集

Java本身不支持差集元算,但是通过交集+补集的形式来实现。比如[[0-9] && [^3-5]]等价于[0-26-9]。同样,也可以通过环视功能来模拟差集,比如(?![3-5])[0-9]和[0-9](?<![3-5])都等价于[0-26-9]

Java在解析字符类时,会按照如下的次序依次执行:

1 字面值转义\x

1 分组[…]

1 范围a-z

1 并集[a-e][i-u]

1 交集[a-z&&[aeiou]]

1 补集[^…]

点号:.

点号可以用来匹配除了行结束符之外的任意字符。但是,如果启用了DOTALL标志,则可以匹配行结束符


// (?s)<span style="font-family: 宋体;">会启用</span><span style="font-family: Consolas;">DOTALL</span><span style="font-family: 宋体;">标志</span>

assertEquals(true,"\n".matches("(?s)."));

assertEquals(true,"\r".matches("(?s)."));

字符类简记法:

Java预定义了如下几种字符组,可以很方便地使用。

o \d数字:[0-9]

o \D非数字:[^0-9]

o \s空白字符:[ \t\n\x0B\f\r] (注意第一个字符是空格)

o \S非空白字符:[^\s]

o \w单词字符:[a-zA-Z_0-9]

o \W非单词字符:[^\w]

Unicode属性和区块:\p{PropOrBlock、\P{PropOrBlock

\p{PropOrBlock表示匹配符合PropOrBlock的所有字符,大写的\P{PropOrBlock则匹配不符合PropOrBlock的所有字符。

PropOrBlock包括字符属性(Char Property)和区块(Block)。区块必须以In开头,字符属性可以以Is开头(可选)。Unicode 区块的定义在java.lang.Character.UnicodeBlock,具体可以查看Unicode标准。字符属性的定义在java.util.regex.Pattern.CharPropertyNames。

部分Unicode区块列表(查看WIKI)属性 说明

\p{InBASIC_LATIN} Basic Latin
\p{InCJK_COMPATIBILITY} 中日韩兼容文字
更多请查看JavaDoc

基本的POSIX字符属性表(查看标准定义)属性 说明

\p{ASCII} ASCII字符: 0×00~0x7F
\p{Lower} 小写字母([a-z])
\p{Upper} 小写字母([A-Z])
\p{Punct} ASCII标点符号
\p{Alpha} ASCII字母([a-zA-Z])
\p{Digit} 数字([0-9]
\p{Alnum} ASCII字母和数字: [a-zA-Z0-9])
\p{Graph} ASCII可打印(可见)字符: [\p{Alnum}\p{Punct}]
\p{Blank} ASCII Blank字符(空格和Tab字符)
\p{Cntrl} ASCII控制字符([\x00-\x1F\x7F]
\p{Print} 可打印字符(0×20~0x7E)
\p{Space} ASCII Space字符([ \t\n\x0B\f\r])
\p{XDigit} ASCII十六进制字符([0-9a-fA-F])

Java补充的字符属性表(定义于Character)属性 说明

\p{javaLowerCase} 等效于 java.lang.Character.isLowerCase()
\p{javaUpperCase} 等效于 java.lang.Character.isUpperCase()
\p{javaTitleCase} 等效于 java.lang.Character.isTitleCase()
\p{javaDigit} 等效于 java.lang.Character.isDigit()
\p{javaDefined} 等效于 java.lang.Character.isDefined()
\p{javaLetter} 等效于 java.lang.Character.isLetter()
\p{javaLetterOrDigit} 等效于 java.lang.Character.isLetterOrDigit()
\p{javaJavaIdentifierStart} 等效于 java.lang.Character.isJavaIdentifierStart()
\p{javaJavaIdentifierPart} 等效于 java.lang.Character.isJavaIdentifierPart()
\p{javaUnicodeIdentifierStart} 等效于 java.lang.Character.isUnicodeIdentifierStart()
\p{javaUnicodeIdentifierPart} 等效于 java.lang.Character.isUnicodeIdentifierPart()
\p{javaIdentifierIgnorable} 等效于 java.lang.Character.isIdentifierIgnorable()
\p{javaSpaceChar} 等效于 java.lang.Character.isSpaceChar()
\p{javaISOControl} 等效于 java.lang.Character.isISOControl()
\p{javaWhitespace} 等效于 java.lang.Character.isWhitespace()
\p{javaMirrored} 等效于 java.lang.Character.isMirrored()

Unicode字符属性表(查看标准定义)属性 说明

\p{L} Letter: 字母
\p{M} Mark: 标记字符,不能单独出现,必须与其他基本字符同时出现(如重音符号)。
\p{N} Number: 各种数字字符
\p{Z} Separator: 分隔字符,但是本身不可见(比如空格)
\p{P} Punctuation: 标点符号
\p{S} Symbol: 各种图形符号和字母符号
\p{C} Other: 其他任何字符
\p{Ll} Character.LOWERCASE_LETTER: 小写字母
\p{Lu} Character.UPPERCASE_LETTER: 大写字母
\p{Lt} Character.TITLECASE_LETTER: 出现在单词开头的字母
\p{Lm} Character.MODIFIER_LETTER: 少数形似字母的,有特别用途的字符
\p{Lo} Character.OTHER_LETTER: 没有大小写形式,也不属于修饰符的字母。
\p{LC} L1,Lu,Lt
\p{LD} LC,Lm,Lo,Nd
\p{L1} Latin-1(0×00~0xff)
\p{Mn} Character.NON_SPACING_MARK
\p{Mc} Character.COMBINING_SPACING_MARK
\p{Me} Character.ENCLOSING_MARK
\p{Nd} Character.DECIMAL_DIGIT_NUMBER
\p{Nl} Character.LETTER_NUMBER
\p{No} Character.OTHER_NUMBER
\p{Zs} Character.SPACE_SEPARATOR
\p{Zl} Character.LINE_SEPARATOR
\p{Zp} Character.PARAGRAPH_SEPARATOR
p{Cc} Character.CONTROL
p{Cf} Character.FORMAT
p{Co} Character.PRIVATE_USE
p{Cs} Character.SURROGATE
p{Pd} Character.DASH_PUNCTUATION
p{Ps} Character.START_PUNCTUATION
p{Pe} Character.END_PUNCTUATION
p{Pc} Character.CONNECTOR_PUNCTUATION
p{Po} Character.OTHER_PUNCTUATION
p{Sm} Character.MATH_SYMBOL
p{Sc} Character.CURRENCY_SYMBOL
p{Sk} Character.MODIFIER_SYMBOL
p{So} Character.OTHER_SYMBOL
p{Pi} Character.INITIAL_QUOTE_PUNCTUATION
p{Pf} Character.FINAL_QUOTE_PUNCTUATION
p{all} 所有字符

锚点及其他零长度断言

锚点及其他零长度断言都是用来匹配一个位置,而不是具体的字符。

起始位置:^\A

脱字符(^)\A会匹配输入文本的起始位置。但是如果启用了MULTILINE模式,^还可以匹配每个行结束符之后的位置。


// \A<span style="font-family: 宋体;">会匹配起始位置</span>

assertEquals(true,"abcde".matches("\\Aabcde"));

// MULTILINE<span style="font-family: 宋体;">模式下,</span><span style="font-family: Consolas;">\A</span><span style="font-family: 宋体;">仍然只匹配起始位置,而不会匹配行结束符之后的位置!</span>

assertEquals(false,"\nabcde".matches("(?m)\n\\Aabcde"));

// ^ <span style="font-family: 宋体;">会匹配起始位置</span>

assertEquals(true,"abcde".matches("^abcde"));

// MULTILINE<span style="font-family: 宋体;">模式下,</span><span style="font-family: Consolas;">^</span><span style="font-family: 宋体;">会匹配行结束符之后的位置!</span>

assertEquals(true,"\nabcde".matches("(?m)\n^abcde"));

// <span style="font-family: 宋体;">甚至这样</span>

assertEquals(true,"\na\nbcde".matches("(?m)\n^a\n^bcde"));

结束位置:$\Z\z

$比较复杂,它的含义依赖于MULTILINE模式是否启用。再次明确一下,行结束符会根据UNIX_LINES模式是否启用而变化。

o 未启用MULTILINE模式(默认)
$待匹配位置之后要么没有任意字符(即严格的结尾),要么只是行结束符,但需要要注意的是,在非UNIX_LINES模式下,它不能在\r\n之间。


// <span style="font-family: 宋体;">匹配严格的结尾</span>

assertEquals(true,"abcde".matches("abcde$"));

// <span style="font-family: 宋体;">匹配严格的结尾</span>

assertEquals(true,"abcde\r".matches("abcde\r$"));

assertEquals(true,"abcde\r\n".matches("abcde\r\n$"));

// <span style="font-family: 宋体;">匹配行结束符之前的位置</span>

assertEquals(true,"abcde\r".matches("abcde$\r"));

assertEquals(true,"abcde\n".matches("abcde$\n"));

assertEquals(true,"abcde\u0085".matches("abcde$\u0085"));

assertEquals(true,"abcde\u2028".matches("abcde$\u2028"));

assertEquals(true,"abcde\u2029".matches("abcde$\u2029"));

assertEquals(true,"abcde\r\n".matches("abcde$\r\n"));

// <span style="font-family: 宋体;">启用</span><span style="font-family: Consolas;">UNIX_LINES</span><span style="font-family: 宋体;">模式后,行结束符只有</span><span style="font-family: Consolas;">\n</span>

assertEquals(false,"abcde\r".matches("(?d)abcde$\r"));

assertEquals(true,"abcde\n".matches("(?d)abcde$\n"));

assertEquals(false,"abcde\u0085".matches("(?d)abcde$\u0085"));

assertEquals(false,"abcde\u2028".matches("(?d)abcde$\u2028"));

assertEquals(false,"abcde\u2029".matches("(?d)abcde$\u2029"));

assertEquals(false,"abcde\r\n".matches("(?d)abcde$\r\n"));

// <span style="font-family: 宋体;">启用</span><span style="font-family: Consolas;">UNIX_LINES</span><span style="font-family: 宋体;">模式后,</span><span style="font-family: Consolas;">$</span><span style="font-family: 宋体;">可以在</span><span style="font-family: Consolas;">\r\n</span><span style="font-family: 宋体;">之间</span>

assertEquals(true,"abcde\r\n".matches("(?d)abcde\r$\n"));

// <span style="font-family: 宋体;">还可以这样匹配</span>

assertEquals(true,"abcde\r\r\n".matches("abcde\r$\r\n"));

assertEquals(true,"abcde\n\r\n".matches("abcde\n$\r\n"));

// <span style="font-family: 宋体;">甚至是这样</span>

assertEquals(true,"abcde\r\n".matches("abcde$\r\n$"));

assertEquals(true,"abcde\r\n".matches("abcde$$\r\n$$"));

// <span style="font-family: 宋体;">但不能匹配</span>

assertEquals(false,"abcde\r\n".matches("abcde\r$\n"));// <span style="font-family: 宋体;">不能匹配</span><span style="font-family: Consolas;">\r\n</span><span style="font-family: 宋体;">之间的位置</span>

assertEquals(false,"abcde\r\n\n".matches("abcde\r$\n\n"));// <span style="font-family: 宋体;">不能匹配</span><span style="font-family: Consolas;">\r\n</span><span style="font-family: 宋体;">之间的位置</span>

assertEquals(false,"abcde\n\n".matches("abcde$\n$\n$"));// <span style="font-family: 宋体;">第一个</span><span style="font-family: Consolas;">$</span><span style="font-family: 宋体;">不满足条件</span>

o 启用MULTILINE模式
$能够匹配输入文本的严格末尾,或者行结束符之前的位置,需要注意的是,在非UNIX_LINES模式下,它同样不能在\r\n之间。


// <span style="font-family: 宋体;">非</span><span style="font-family: Consolas;">MULTILINE</span><span style="font-family: 宋体;">模式下的所有正则表达式,它都可以匹配成功。</span>

// <span style="font-family: 宋体;">但是从下面这个断言可以看出区别</span>

assertEquals(true,"abcde\n\n".matches("(?m)abcde$\n$\n$"));// <span style="font-family: 宋体;">第一个</span><span style="font-family: Consolas;">$</span><span style="font-family: 宋体;">也满足条件</span>

其实MULTILINE模式的启用只是允许$匹配文本中间的行(要不也不叫多行模式了)。

\Z等价于未启用MULTILINE模式的$


// <span style="font-family: 宋体;">与未启用</span><span style="font-family: Consolas;">MULTILINE</span><span style="font-family: 宋体;">模式的</span><span style="font-family: Consolas;">$</span><span style="font-family: 宋体;">一样</span>

assertEquals(true,"abcde".matches("(?m)abcde\\Z"));

assertEquals(true,"abcde\n".matches("(?m)abcde\\Z\n"));

// <span style="font-family: 宋体;">即使正则表达式启用了</span><span style="font-family: Consolas;">MULTILINE</span><span style="font-family: 宋体;">模式,也不能匹配成功</span>

assertEquals(false,"abcde\n\n".matches("(?m)abcde\\Z\n\\Z\n\\Z"));

\z则匹配输入文本的严格末尾,也即要求待匹配位置后面不能有任何字符(包括行结束符)。


// <span style="font-family: 宋体;">匹配末尾</span>

assertEquals(true,"abcde\n".matches("abcde\n\\z"));

// <span style="font-family: 宋体;">后面不能有任何字符,所以不能匹配</span>

assertEquals(false,"abcde\n".matches("abcde\\z\n"));

上次匹配成功的结束位置:\G

在迭代匹配中,有时候需要从上次匹配成功的结束位置继续匹配,就跟循环执行\A一样。

在每次成功匹配之后,Java会保存此次匹配的结束位置(见Matcher.last)。下次匹配时,如果上次起始位置与上次结束位置一样,则强制前进一个字符,防止无穷循环。如果要求匹配\G,则会比较当前的位置是否与上次结束位置相同,相同则匹配成功,否则匹配不成功。


assertEquals("!a!b!c!d!e!","abcde".replaceAll("x?","!"));

单词分界符:\b\B

单词分界符\b用来匹配单词的边界,边界要求一边是单词字母,另一边不是单词字母。所谓单词字母,包括下划线(_)、大小写字母、数字和非空格标记字符(Character.NON_SPACING_MARK),也即[\w\p{Mn}]

\B则匹配不是单词边界的位置。

Java不区分左分界、右分界,而是笼统的边界。可以使用下面介绍的环视功能来区分左右边界,比如(?

顺序环视(?=)(?!);逆序环视(?<=)(?<!)

环视功能可以从当前位置向左或向右匹配执行的正则子表达式。向左查看称作逆序环视,向右查看称作顺序环视。

举例说明,对于字符串1234223432344234(?<=2)234可以匹配到1234223432344234

逆序环视要求长度是确定的,也即最大长度不能使无穷的。比如(?<=books?)是可以的,因为其最大长度是5,但(?<=\w+)是不行的。

注释和模式修饰符

模式修饰符:(?modifier),如(?i)(?-i)

Java允许在正则表达式中使用模式修饰符来设定匹配模式,(?x)开启x模式,(?-x)关闭x模式。如果模式修饰符在括号内部,则其作用范围仅限于括号内部。

比如<B>(?i)text(?-i)</B>,要求两边的TAG必须为大写,而启用的内容text则不关系大小写。<B>(?:(?i)text)</B>也是相同的含义,因为(?i)只作用于括号内部。


// <B>(?i)text(?-i)</B>

assertEquals(true,"<B>text</B>".matches("<B>(?i)text(?-i)</B>"));

assertEquals(true,"<B>TEXT</B>".matches("<B>(?i)text(?-i)</B>"));

assertEquals(false,"<b>text</B>".matches("<B>(?i)text(?-i)</B>"));

// <B>(?:(?i)text)</B>

assertEquals(true,"<B>text</B>".matches("<B>(?:(?i)text)</B>"));

assertEquals(true,"<B>TEXT</B>".matches("<B>(?:(?i)text)</B>"));

assertEquals(false,"<b>text</B>".matches("<B>(?:(?i)text)</B>"));

模式修饰符字母列表字母 模式

i 对应于Pattern.CASE_INSENSITIVE标志不区分大小写
d 对应于Pattern.UNIX_LINES标志:只有\n被视为行结束符,\r等不再被视为行结束符
u 对应于Pattern.UNICODE_CASE标志:当启用CASE_INSENSITIVE模式时,忽略大小写时会支持Unicode字符,而不仅仅是ASCII字符。具体地,此标志下,会使用Character.toUpperCase/toLowerCase来转换大小写。
x 对应于Pattern.COMMENTS标志:忽略空白字符(即\s代表的字符),忽略#和行结束符之前的内容(同时也忽略行结束符)。
m 对应于Pattern.MULTILINE标志:使得^$可以匹配文本中间的行结束符之前和之后的位置。具体见^$
s 对应于Pattern.DOTALL标志:点号.可以匹配行结束符

局部模式修饰符:(?modifier:),如(?i:)

要限制模式修饰符的作用范围,除了将之放于括号内部,也可以简单地使用(?x:)形式。比如<B>(?:(?i)text)</B>可以简化成<B>(?i:text)</B>。需要注意的是,虽然与括号形式类似,但它并不是捕获组,无法捕获内容。

文本范围: \Q\E

\Q\E会把其内部的字符当作普通文本来对待,而不是视为正则表达式。特别需要注意的是内部文本不能包含\E,可以使用\Q\E\\E\Q\E来代替。


// match: \s <--- \Q\s\E

assertEquals(true,"\\s\\t".matches("\\Q\\s\\t\\E"));

// match: \E <--- \Q\s\E\\E\Q\t\E

assertEquals(true,"\\s\\E\\t".matches("\\Q\\s\\E\\\\E\\Q\\t\\E"));

分组和捕获

捕获/分组括号:()\1\2

普通的没有特殊含义的括号通用用于分组和捕获,形式为(…)。

对于捕获的分组,可以使用反向引用来获取子表达式匹配的文本。Java使用\1\2形式的反向引用,后面的数字表示分组的编号。分组编号是按照左括号(出现的次序来分配的。在Java中,分组编号的数目是没有限制的。


// <span style="font-family: 宋体;">【样例</span><span style="font-family: Consolas;">1</span><span style="font-family: 宋体;">】捕获分组</span><span style="font-family: Consolas;">1</span><span style="font-family: 宋体;">:</span><span style="font-family: Consolas;">abc</span>

assertEquals(true,"abc1abc".matches("(\\w+)1\\1"));

// <span style="font-family: 宋体;">【样例</span><span style="font-family: Consolas;">2</span><span style="font-family: 宋体;">】捕获分组</span><span style="font-family: Consolas;">1</span><span style="font-family: 宋体;">:</span><span style="font-family: Consolas;">a</span><span style="font-family: 宋体;">,分组</span><span style="font-family: Consolas;">2</span><span style="font-family: 宋体;">:</span><span style="font-family: Consolas;">b</span><span style="font-family: 宋体;">,分组</span><span style="font-family: Consolas;">3</span><span style="font-family: 宋体;">:</span><span style="font-family: Consolas;">c</span>

assertEquals(true,"abc1cba".matches("(\\w)(\\w)(\\w)1\\3\\2\\1"));

// <span style="font-family: 宋体;">【样例</span><span style="font-family: Consolas;">3</span><span style="font-family: 宋体;">】一个很奇怪的例子,这与</span><span style="font-family: Consolas;">Java</span><span style="font-family: 宋体;">在匹配分组循环时回溯逻辑有关,具体原因不便讨论。</span>

assertEquals(true,"abc11".matches("(\\w)+1\\1"));

仅分组不捕获的括号:(?:)

(?:)仅用于分组,但不能用来提取文本。

固化分组:(?>)

固化意思是说,一旦括号内的子表达式匹配成功之后,匹配的内容就被固化,在接下来的匹配过程中是不变的,除非整个子表达式被弃用。


assertEquals(true,"1abcde2".matches("1.*2"));

// .*<span style="font-family: 宋体;">匹配到</span><span style="font-family: Consolas;">abcde2</span><span style="font-family: 宋体;">之后,将会被固化,但是之后的</span><span style="font-family: Consolas;">2</span><span style="font-family: 宋体;">无法匹配成功,需要强迫</span><span style="font-family: Consolas;">.*</span><span style="font-family: 宋体;">释放最后匹配的内容(即</span><span style="font-family: Consolas;">e</span><span style="font-family: 宋体;">),但是由于是固化分组,这个操作无法实现。</span>

assertEquals(false,"1abcde2".matches("1(?>.*)2"));

多选分支:||

多选分支可以用来在同一个位置测试多个子表达式。Java会按照从左到右的次序来匹配。子表达式可以为空表达式,比如(?:abc|)等价于(?:abc)?

量词

匹配优先量词:*+?{num,num}

两次可以限制作用对象的匹配次数。*表示匹配次数零次或多次,+表示匹配次数一次或多次,?表示匹配次数零次或一次,{cmin,cmax}表示匹配次数为cmincmax次(均包含边界)。

需要注意,X{0,0}的意思不是不出现X,而是不进行任何匹配,也即跟没有是一样的。如果要实现不出现X,请使用否定环视功能。

忽略优先量词:*?+???{num,num}?

默认情况下,量词是匹配优先的,也即匹配尽可能多的内容。而忽略优先则相反,会匹配尽可能少的内容。

占有优先量词:*+++?+{num,num}+

占有优先与固化分组类似,一旦匹配某些内容,将不会交还这些内容,除非整个子表达式被弃用。往往可以使用占有量词来优化正则表达式。

 

原文地址:http://thihy.iteye.com/blog/1777065