Java正则表达式(Java Regular Expression)

       也不知道是谁首先将Regular Expression翻译成了"正则表达式"这么一个文诌诌的名词。无论是按字面还是按其用途,Regular Expression都应该是规则表达式,指对特定字符串构成规则/特征的描述。这种构成规则,也就是模式(Pattern)。

      最早应用在Unix系统中的正则表达式尽管没有一个统一的权威标准,但各种正则表达式可说是大同、小异。

    Java语言在JDK1.4版本中内置了java.util.regex,提供对正则表达式的支持, 其中包含Pattern、Matcher、PatternSyntaxException三个部分。

1. 正则表达式中的字符和字符集

构成字符串的最基本元素是字符,正则表达式中,字符可以是字母/数字等可打印字符,也可以是回车/换行等控制字符。

1.1 基本字符

字符
c 字符c(可打印字符)
\xhh 16进制数值0xhh表示的字符。如字母'a'可以表示成0x61
\0vvv 8进制数值0vvv表示的字符
\uhhhh 16进制数值0xhhhh表示的Unicode字符
\t 制表符(Tab, 0x09)
\n 换行符(LF, 0x0a)
\r 回车符(CR, 0x0d)
\f 换页符(FF, 0x0c)
\e 转义符(ESC, 0x1b)
\cx 控制字符x
\\ 字符'\'。字符'\'被用转义控制符,为了表示字符'\',就只好用'\\'表示

1.2 字符类

Java正则表达式中,还可以用'[...]'的形式表示一类特定字符中的一个。如[abc],指a或b或c;[0-9]表示数字0到9中的某一个;[abc[lmn]]表示a|b|c|l|m|n。[^abc]表示除a/b/c外的其他任意字符;[a-z-[bcd]]则表示从[a-z]的集合中减掉集合[bcd]...。

Java中,提供了一些内置的预定义字符类,以方便使用。

内置字符类
. 除终止符外的其他任意单个字符。如果标志位DotAll有效,则表示任意字符
\d 数字0-9,等同于[0-9]
\D 除数字0-9外的其他字符,等同于[^0-9]或[^\d]
\s 空白符,空格/制表/回车/换行/换页符,等同于[ \t\n\x0B\f\r]
\S 非空白符,等同于[^\s]
\w 英语中单词中所使用的字符,指a-z, A-Z, 0-9, 或连字符'-',等同于[0-9_A-Za-z]
\W 非单词字符,除\w外的其他字符[^\w]

POSIX字符类(仅应用于us-ascii)

POSIX字符类
\p{Lower} 小写字母, [z-z]
\p{Upper} 大写字母, [A-Z]
\p{ASCII} ASCII字符, [\x00-\x7f]
\p{Alpha} 字母, [\p{Lower}\p{Upper}]
\p{Digit} 数字, [0-9]
\p{Alnum} 字母或数字, [\p{Alpha}\p{Digit}]
\p{Punct} 标点, [!"#$%&'()*+,-./:;<=>?@[]\^_{}|~]
\p{Graph} 可见字符, [\p{Alnum}\p{Punct}]
\p{Print} 可打印字符, [\p{Graph}\s]
\p{Blank} 空格或制表符, [ \t]
\p{Cntrl} 控制字符, [\x00-\x1f\x7f]
\p{XDigit} 16进制的一位数字, [0-9a-fA-F]
\p{Space} 空白符号, [ \t\n\f\r\0x0b]
\P{name}, name是以上名称中的一个. 表示名称name指定字符集外的字符, [^\p{name}]

边界字符

边界符
^ 输入起始;多行模式下,指一行的开始
$ 输入结束;多行模式下,指一行的终止
\b 单词的边界
\B 非单词的边界
\A 输入的开始
\z 输入的结束
\Z 最后结尾外的输入结束
\G 上一个匹配的结束

引用控制:

\Q...\E 逐字引用\Q和\E之间的字符

更多内置字符类, 可以在Java文档中java.util.regex.Pattern部分找到.

2. 逻辑操作符

Java提供了一些逻辑操作符,用来对字符/字符类进行简单的逻辑操作

逻辑操作符
XY 模式X后紧跟模式Y
X|Y X或者Y。从功能角度看,X|Y等同于字符类[XY],但从效率上看,字符类优于逻辑或。因此,应尽可能使用字符类而不是逻辑操作或。
&& 与操作,求交集
^ 求反/补集, 如上面的[^\d]等同于\D

3. 量词

量词,用来对模式的匹配数量进行限制.

3.1 基本量词

基本量词
? 匹配0或1次
* 匹配0或任意次
+ 匹配1次或多次
{n} 匹配n次
{n+} 匹配n次或更多次
{n, m} 匹配n到m次

量词在应用时,总是会结合它左边的第一个模式. 如,模式abc+,会匹配abc, abcc, abccc..., 而不会匹配abcabc. 因此, 括号的使用在某些情况下是必要的.

3.2 量词匹配方式

Java中,提供了三种不同量词匹配方式.

  • 贪婪型(Greedy) : 默认方式. 尽可能多地使用量词所修饰的模式去匹配字符串, 直至找到匹配为止
  • 勉强型(Reluctant) : 最小匹配,尽可能少地使用量词所修饰模式去匹配字符串。在基本匹配量词后附加一个'?'标记使用此种方式
  • 占用型(Possessive) : 占用量词所修饰模式所匹配的全部字符,将剩余部分留给模式的其他部分去匹配. 在基本匹配量词后附加一个'+'标记使用此种方式
Java文档中的例子
假定要分析的字符串是 xfooxxxxxxfoo
  • 模式.*foo : 模式分为子模式p1(.*)和子模式p2(foo)两个部分. 其中p1中的量词匹配方式使用默认方式(贪婪型). 匹配开始时,吃入所有字符xfooxxxxxx去匹配子模式p1. 匹配成功,但这样以来就没有了字符串去匹配子模式p2. 本轮匹配失败;第二轮:减少p1部分的匹配量,吐出最后一个字符, 把字符串分割成xfooxxxxxxfoo两个子字符串s1和s2. s1匹配p1, 但s2不匹配p2. 本轮匹配失败;第三轮,再次减少p1部分匹配量,吐出两个字符, 字符串被分割成xfooxxxxxxfooo两部分. 结果同上。第四轮,再次减少p1匹配量, 字符串分割成xfooxxxxxxfoo两个部分, 这次s1/s2分别和p1/p2匹配. 停止尝试,返回匹配成功.
  • 模式.*?foo : 最小匹配方式. 第一次尝试匹配, p1由于是0或任意次,因此被忽略,用字符串去匹配p2,失败;第二次,读入第一个字符x, 尝试和p1匹配, 匹配成功; 字符串剩余部分fooxxxxxxfoo中前三个字符和p2也是匹配的. 因此, 停止尝试, 返回匹配成功.在这种模式下,如果对剩余字符串继续去寻找和模式相匹配的子字符串,还会找到字符串末尾的另一个xfoo,而在贪婪模式下,由于第一次匹配成功的子串就已经是所有字符,因此不存在第二个匹配子串。
  • 模式.*+foo : 占用模式. 匹配开始时读入所有字符串, 和p1匹配成功, 但没有剩余字符串去和p2匹配. 因此, 匹配失败. 返回.

简单地说, 贪婪模式和占有模式相比, 贪婪模式会在只有部分匹配成功的条件下, 依次从多到少减少匹配成功部分模式的匹配数量, 将字符留给模式其他部分去匹配; 而占用模式则是占有所有能匹配成功部分, 绝不留给其他部分使用.

4. 匹配控制标志

在字符串匹配中,通常还需要注意大小写,换行风格(win下是\r\n,unix/linux则是\n), Unicode等等。Java正则表达式中,可以通过(?idmsux)设定或通过(?-idmsux)取消标志,方便用户自行控制。

控制标志
i 忽略大小写,也可在Pattern.compile方法中通过CASE_INSENSITIVE设置
d Unix风格(\n)换行符,也可在Pattern.compile方法中通过UNIX_LINES设置
m 多行模式,也可在Pattern.compile方法中通过MULTILINE设置
s DotAll标志. 通配符.是否包括\n\r。也可在Pattern.compile方法中通过DOTALL设置
u Unicode模式。也可在Pattern.compile方法中通过UNICODE_CASE设置
x 注释标志. 在模式中可使用注释,并忽略模式中的空白符号和'#'号,直到模式尾部。也可在Pattern.compile方法中通过COMMENTS设置
在Pattern.compile()方法,还可以指定CANON_ON标志,用来定义匹配需遵守UNICODE等价规范。参考资料

5. 分组(Group)

分组, 即将将多个字符编组合在一起, 作为其他操作的一个逻辑单位. Java中, 以圆括号'(...)'作为分组的标识. 比如, 使用量词时,常会用到分组来指定要重复的单位.

Java正则表达式中, 分组可以分为捕获组(Capturing Group)和非捕获组(Non-Capturing Group)两类.

5.1 捕获组

实际应用中, 对字符串进行匹配操作时, 除了字符串是否匹配模式的答案外, 常常还会关心诸如字符串中有多少与模式相匹配的子串, 与模式相匹配的子串到底是什么样等更具体的问题. 某些情况下, 前面的捕获结果还会直接成为后面捕获模式的一部分. 这些都 需要在匹配过程中,将实际匹配的子串储存起来(也许还需要计数), 以供随后可能的使用.

Java正则表达式中, 默认分组都是捕获组, 也即分组模式匹配的结果会被缓存起来. 在存在多个分组的模式中, Java从左到右扫描模式中的括号, 依次给存在的分组进行编号.

模式 ((A)(B(C)))中存在4个分组, 它们是:
0. ((A)(B(C))) 最外层括号所包含的模式。也即分组0永远都是模式本身。
1. (A) 从左向右,遇到第二个'('及相应')'构成的模式。
2. (B(C)) '('及相应')'构成的模式。
3. (C) '('及相应')'构成的模式。

除了在完成匹配后使用Java所缓存的结果外, 在匹配开始前就可以在匹配模式中通过'\i'的形式引用第i个分组可能匹配到的实际字符串. 这种引用模式被称为后置引用(Back Quote).

例:
假如要匹配用成对单引号'或双引号"引用的字符串, 可以指定匹配模式为: (['"]).*\1。 模式匹配'abc', 或"abc"等,但不会匹配'abc"或"ab等字符串。

5.2 非捕获组

并非所有分组的匹配结果都必须缓存。以'(?...)'形式指定的分组被称为非捕获组。匹配过程中,非捕获组所匹配的实际内存不会被缓存,匹配数量也不会被统计。因此,非捕获组不会获得组编号,也不能被反向引用。

非捕获组的几种形式

表达式 意义
(?:X) 指定模式X为非捕获组,但不保存其匹配结果。
如:(?:abc){3}匹配abcabcabc,但不缓存分组(abc)的匹配结果,更不能反向引用匹配结果
(?=X) X为非捕获分组;并且,(?=X)匹配成功后不会占用任何字符!也即,形如(?=X)Y的模式中,子模式Y开始匹配的地方是非捕获组X匹配成功的开始处而不是结束处!
如:Jack(?=Sprat|Frost), 匹配字符串JackSprat中的Jack
\w+(?=\d)匹配任何以数字结尾的单词,返回结果不包含数字
(?=two)three不能匹配字符串onetwothree,(?=two)([a-z])则会匹配以上字符串中的t
Y(?!X) X为非捕获分组,模式匹配任何后面不是X的Y。
如: Jack(?!Sprat>匹配JackFrost,但不会匹配JackSrpat
\w+(?!\d)匹配任何不以数字结尾的单词,返回结果中不包含最后一个字符
(?<=X)Y X为非捕获分组,模式匹配前面有X的Y。如使用量词,则量词所指定重复次数必须是一个有限集合。如(?<=foo)bar匹配foobar中的bar
(?<=\w{2,3})\d匹配前面为2或3个单词字符的一位数字,如ab3中的3
(<=w+)\d非法,量词+可能匹配次数为无限。
(? X为非捕获分组,模式匹配前面不是X的Y。如使用量词,则量词所指定重复次数必须是一个有限集合。如(?匹配任何不是以20开始的10
(?>X) X为独立、非捕获分组。一旦X匹配成功,则不允许匹配引擎进行回退操作(贪婪模式下的吐字符)。
如:模式(?>[ab]*)\w\w,不能在字符串aabbaa中找到匹配子串。原因是(贪婪模式下)初次匹配时,[ab]*会将整个字符串占为己有,即使第一轮匹配失败也不会减少匹配量进行下一轮的尝试。而(?:[ab]*)\w\w)则可以匹配成功。

你可能感兴趣的:(java)