正则表达式是一种强大而灵活的文本处理工具。使用正则表达式,我们能以编程的方式构造复杂的文本模式,并对输入的字符串进行搜索。正则表达式提供了一种完全通用的方式,能够解决各种字符串处理相关的问题:匹配、选择、编辑以及验证。
java.util.regex 包
java.util.regex 包主要包含用于匹配字符序列与正则表达式指定模式的类。
MatchResult接口
MatchResult:匹配操作的结果。
此接口包含用于确定与正则表达式匹配结果的查询方法。通过 MatchResult 可以查看匹配边界、组和组边界,但是不能修改。
Matcher类
Matcher:通过解释 Pattern 对 character sequence 执行匹配操作的引擎。类的实例用于匹配字符序列与给定模式。通过 CharSequence 接口将输入提供给匹配器,以支持从多种输入源到字符的匹配。
通过调用模式的 matcher 方法从模式创建匹配器。创建匹配器后,可以使用它执行三种不同的匹配操作:
• matches 方法尝试将整个输入序列与该模式匹配。
• lookingAt 尝试将输入序列从头开始与该模式匹配。
• find 方法扫描输入序列以查找与该模式匹配的下一个子序列。
每个方法都返回一个表示成功或失败的布尔值。通过查询匹配器的状态可以获取关于成功匹配的更多信息。
匹配器在其输入的子集(称为区域)中查找匹配项。默认情况下,此区域包含全部的匹配器输入。可通过 region 方法修改区域,通过 regionStart 和 regionEnd 方法查询区域。区域边界与某些模式构造交互的方式是可以更改的。有关此内容更多的信息,请参阅 useAnchoringBounds 和 useTransparentBounds。
此类还定义使用新字符串替换匹配子序列的方法,需要时,可以从匹配结果计算出新字符串的内容。可以先后使用 appendReplacement 和 appendTail 方法将结果收集到现有的字符串缓冲区,或者使用更加便捷的 replaceAll 方法创建一个可以在其中替换输入序列中每个匹配子序列的字符串。
匹配器的显式状态包括最近成功匹配的开始和结束索引。它还包括模式中每个捕获组捕获的输入子序列的开始和结束索引以及该子序列的总数。出于方便的考虑,还提供了以字符串的形式返回这些已捕获子序列的方法。
匹配器的显式状态最初是未定义的;在成功匹配导致 IllegalStateException 抛出之前尝试查询其中的任何部分。每个匹配操作都将重新计算匹配器的显式状态。
匹配器的隐式状态包括输入字符序列和添加位置,添加位置最初是零,然后由 appendReplacement 方法更新。
可以通过调用匹配器的 reset() 方法来显式重置匹配器,如果需要新输入序列,则调用其 reset(CharSequence) 方法。重置匹配器将放弃其显式状态信息并将添加位置设置为零。
此类的实例用于多个并发线程是不安全的。
Pattern类
Pattern:正则表达式的编译表示形式。
指定为字符串的正则表达式必须首先被编译为此类的实例。然后,可将得到的模式用于创建 Matcher 对象,依照正则表达式,该对象可以与任意字符序列匹配。执行匹配所涉及的所有状态都驻留在匹配器中,所以多个匹配器可以共享同一模式。
因此,典型的调用顺序是
Pattern p = Pattern.compile(“a*b”);
Matcher m = p.matcher(“aaaaab”);
boolean b = m.matches();
在仅使用一次正则表达式时,可以方便地通过此类定义 matches 方法。此方法编译表达式并在单个调用中将输入序列与其匹配。语句
boolean b = Pattern.matches(“a*b”, “aaaaab”);
此类的实例是不可变的,可供多个并发线程安全使用。Matcher 类的实例用于此目的则不安全。
PatternSyntaxException异常类
PatternSyntaxException:抛出未经检查的异常,表明正则表达式模式中的语法错误。
正则表达式的构造
在其他语言中,\ 表示:我想要在正则表达式中插入一个普通的(字面上的)反斜杠,请不要给它任何特殊的意义。
在 Java 中,\ 表示:我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。
所以,在其他的语言中,一个反斜杠\就足以具有转义的作用,而在正则表达式中则需要有两个反斜杠才能被解析为其他语言中的转义作用。也可以简单的理解在正则表达式中,两个 \ 代表其他语言中的一个 \,这也就是为什么表示一位数字的正则表达式是 \d,而表示一个普通的反斜杠是 \\。
字符
构造 | 匹配 |
---|---|
x | 字符 x |
\ | 反斜线字符 |
\0n | 带有八进制值 0 的字符 n (0 <= n <= 7) |
\0nn | 带有八进制值 0 的字符 nn (0 <= n <= 7) |
\0mnn | 带有八进制值 0 的字符 mnn(0 <= m <= 3、0 <= n <= 7) |
\xhh | 带有十六进制值 0x 的字符 hh |
\uhhhh | 带有十六进制值 0x 的字符 hhhh |
\t | 制表符 (‘\u0009’) |
\n | 新行(换行)符 (‘\u000A’) |
\r | 回车符 (‘\u000D’) |
\f | 换页符 (‘\u000C’) |
\a | 报警 (bell) 符 (‘\u0007’) |
\e | 转义符 (‘\u001B’) |
\cx | 对应于 x 的控制符 |
字符类
构造 | 匹配 |
---|---|
[abc] | a、b 或 c(简单类) |
[^abc] | 任何字符,除了 a、b 或 c(否定) |
[a-zA-Z] | a 到 z 或 A 到 Z,两头的字母包括在内(范围) |
[a-d[m-p]] | a 到 d 或 m 到 p:[a-dm-p](并集) |
[a-z&&[def]] | d、e 或 f(交集) |
[a-z&&[^bc]] | a 到 z,除了 b 和 c:[ad-z](减去) |
[a-z&&[^m-p]] | a 到 z,而非 m 到 p:[a-lq-z](减去) |
预定义字符类
构造 | 匹配 |
---|---|
. | 任何字符(与行结束符可能匹配也可能不匹配) |
\d | 数字:[0-9] |
\D | 非数字: [^0-9] |
\s | 空白字符:[ \t\n\x0B\f\r] |
\S | 非空白字符:[^\s] |
\w | 单词字符:[a-zA-Z_0-9] |
\W | 非单词字符:[^\w] |
边界匹配器
构造 | 匹配 |
---|---|
^ | 行的开头 |
$ | 行的结尾 |
\b | 单词边界 |
\B | 非单词边界 |
\A | 输入的开头 |
\G | 上一个匹配的结尾 |
\Z | 输入的结尾,仅用于最后的结束符(如果有的话) |
\z | 输入的结尾 |
数量词
贪婪型 | 勉强型 | 占有型 | 匹配 |
---|---|---|---|
X? | X?? | X?+ | X,一次或一次也没有 |
X* | X*? | X*+ | X,零次或多次 |
X+ | X+? | X++ | X,一次或多次 |
X{n} | X{n}? | X{n}+ | X,恰好 n 次 |
X{n,} | X{n,}? | X{n,}+ | X,至少 n 次 |
X{n,m} | X{n,m}? | X{n,m}+ | X,至少 n 次,但是不超过 m 次 |
量词是描述一个模式吸收输入文本的方式:
贪婪型:贪婪表达式会为所有可能的模式发现就可能多的匹配。导致此问题的一个典型理由就是假定我们的模式仅能匹配第一个可能的字符组,如果它是贪婪的,那么它就会继续往下匹配。
勉强型:用问号来指定,这个量词匹配满足模式所需的最少字符数。因此也称为懒惰的。
占有型:当正则表达式被应用于字符串时,它会产生相当多的状态,以便在匹配失败时可以回溯。而”占有的“量词并不保存这些中间状态,因此它们可以防止回溯。它们常常用来防止正则表达式失控,因此可以使正则表达式执行起来更有效。
举个例子:
贪婪型:会尽量大范围的匹配,直到匹配了整个内容,这时发现匹配不能成功时,就回溯。所以它会匹配最长的字串。
Matcher m1 = Pattern.compile("a*a")
.matcher("baaaaaab");
while(m1.find()){
System.out.println(m1.group());
}
控制台:
aaaaaa
勉强型:先从最小匹配开始,先从左端吞入一个字符,然后进行匹配,若不匹配就再吞入一个字符,直到找到匹配或将整个字符串吞入为止。
Matcher m1=Pattern.compile("a*?a")
.matcher("baaaaaab");
while(m1.find()){
System.out.println(m1.group());
}
控制台:
a
a
a
a
a
a
占有型:+是占有优先,也就是说+前面的字符会尽可能匹配,匹配了的就不会再回溯。
Matcher m1=Pattern.compile("a*+a")
.matcher("baaaaaab");
while(m1.find()){
System.out.println(m1.group());
}
此时匹配不到。
逻辑运算符
操作符 | 匹配 |
---|---|
XY | X 后跟 Y |
X 或 Y | |
(X) | X,作为捕获组 |
捕获组:
捕获组可以通过从左到右计算其开括号来编号,组零始终代表整个表达式。
之所以这样命名捕获组是因为在匹配中,保存了与这些组匹配的输入序列的每个子序列。捕获的子序列稍后可以通过 Back 引用在表达式中使用,也可以在匹配操作完成后从匹配器获取。
与组关联的捕获输入始终是与组最近匹配的子序列。如果由于量化的缘故再次计算了组,则在第二次计算失败时将保留其以前捕获的值(如果有的话)例如,将字符串 “aba” 与表达式 (a(b)?)+ 相匹配,会将第二组设置为 “b”。在每个匹配的开头,所有捕获的输入都会被丢弃。
以 (?:) 开头的组是纯的非捕获 组,它不捕获文本,也不针对组合计进行计数。
//有4个捕获组:
//0:(\\d+)(('[a-e])*)'[a-z]
//1:(\\d+)
//2:(('[a-e])*)
//3:('[a-e])
Pattern p = Pattern.compile("(\\d+)(('[a-e])*)'[a-z]");
Matcher m = p.matcher("123'a'b'c'd'e'f'g");
System.out.println("Group Count: " + m.groupCount());
if (m.find( )) {
System.out.println("Found value: " + m.group(0));
System.out.println("Found value: " + m.group(1));
System.out.println("Found value: " + m.group(2));
System.out.println("Found value: " + m.group(3));
}
Group Count: 3
Found value: 123'a'b'c'd'e'f
Found value: 123
Found value: 'a'b'c'd'e
Found value: 'e