原文发于http://blog.thihy.info/post/119,转载请注明出处。
本文是在学习正则表达式过程中整理的,虽然冠以“教程”,但实际上应该算是学习笔记。整篇文章需要对正则有一定的理解。。如果有啥写得不对的,或者写得不够清楚的,欢迎大家留言讨论。
概述
正则表达式(Regular Expression)是高效的、便捷的文本处理工具,能够快速查询符合某种规范的文本。
例如:[0-9]{3}
可以匹配3位数字,[a-z]{3}
则可以匹配3个小写字母。
目前正则表达式被众多工具所支持,比如egrep、sed、perl、ruby、Java、C#、python、Tcl等,不同的工具下,正在表达式的范式可能会有略微的差别,执行引擎也可能不同。目前,正则引擎主要有:DFA, 传统型NFA, POSIX NFA, DFA/NFA混合。本文主要介绍Java正则表达式,它的引擎属于传统型NFA。
Java正则支持Unicode,它在适当的时候,会使用java.lang.Character.codePointAt(CharSequence
seq, int index)
获取Code Point
,而不是char。
构造方法
Java中的正则表达式的构造方法可以看Pattern的javadoc文档。
字符
对于可见字符,可以直接编写,这没啥难点。对于其它难以描述的字符,Java提供了一些表示方法。
字符缩略表示法
Java执行使用\x
来代表特殊的含义,有:
\\
\t
\n
\r
\f
\a
\e
\v
控制字符:
\cchar
Java可以使用\cchar
匹配控制字符。其中char
的值为64
^ 控制字符
,比如对于退格符(‘\b
‘),其ASCII码为8,则char
为64
^ 8 = 72
,也即H
(H的ASCII码为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-f或A-F。\x00和\xff都是合法的,但是\xa,\x0ab,\x1g都是不合法的。
Unicode转义:\uhhhh
Unicode转义的形式与十六进制类似,只是后面必须是四个六进制字符。
行结束符
行结束符是用来标记输入字符序列的行结尾,可能有一个或两个字符。在Java中,行结束符包括:
- 新行(换行)符 (‘\n’)
- 后面紧跟新行符的回车符 (“\r\n”)
- 单独的回车符 (‘\r’)
- 下一行字符 (‘\u0085′)
- 行分隔符 (‘\u2028′)
- 段落分隔符 (‘\u2029)
如果启用了UNIX_LINES模式,则新行符(即’\n’)是惟一识别的行结束符。
只有启用DOTALL标志,正则表达式中的点号(即’.')才会匹配行结束符。
默认情况下,正则表达式^和$会忽略行结束符,仅分别与整个输入序列的开头和结尾匹配。如果激活 MULTILINE 模式,则 ^ 在输入的开头和行结束符之后(输入的结尾)才发生匹配。处于 MULTILINE 模式中时,$ 仅在行结束符之前或输入序列的结尾处匹配。
字符类
字符类:
[...]
字符类的形式是[...]
,其内部可以是若干字符,也可以是一个字符范围。比如[a]表示匹配a字符,[a-z]表示匹配所有小写的英文字母。字符范围中的字符可以是Unicode字符,并且不要求是同一类的,也即[a-}]
也是可以的,甚至是[a-星]
。
字符类集合运算
字符类可以进行补集、并集(隐式)、交集和差集的运算。所有的集合运算都必须在字符类内部实现。
^
开头,则表示是一个补集。比如[^a]表示匹配不是a的所有字符,这与[a^]是不同的,后者表示匹配a字符或^字符。既然是补集,那么需要明确全集是什么。由于Java是支持Unicode的,所以全集是所有的Unicode字符。也即[^a]可以匹配汉字字符。注:很多书籍上(包括JavaDoc)都没有谈到补集的概念,而是作为基本的字符类,但我觉得成为补集操作更加便于理解。
&&
。例如[[1-5]&&[3-9]]等价于[3-5]。通过环视功能可以模拟交集运算,比如(?=[1-5])[3-9]
和[3-9](?<=[1-5])
都等价于[3-5]。
(?![3-5])[0-9]
和[0-9](?<![3-5])
都等价于[0-26-9]。
Java在解析字符类时,会按照如下的次序依次执行:
- 字面值转义 \x
- 分组 [...]
- 范围 a-z
- 并集 [a-e][i-u]
- 交集 [a-z&&[aeiou]]
- 补集 [^...]
点号:.
点号可以用来匹配除了行结束符之外的任意字符。但是,如果启用了DOTALL标志,则可以匹配行结束符。
1
2
3
|
//
(?s)会启用DOTALL标志 assertEquals( true ,
"\n" .matches( "(?s)." )); assertEquals( true ,
"\r" .matches( "(?s)." )); |
字符类简记法:
Java预定义了如下几种字符组,可以很方便地使用。
\d
数字:[0-9]\D
非数字:[^0-9]\s
空白字符:[ \t\n\x0B\f\r] (注意第一个字符是空格)\S
非空白字符:[^\s]\w
单词字符:[a-zA-Z_0-9]\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
。
\p{InBASIC_LATIN} | Basic Latin |
\p{InCJK_COMPATIBILITY} | 中日韩兼容文字 |
更多请查看JavaDoc |
\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]) |