Java正则表达式完全攻略(非常详细,附带实例)
正则表达式用于指定字符串模式,可以使用正则表达式来定位与特定模式匹配的字符串。
例如,假设你想在 HTML 文件中查找超链接,需要查找 <a href="..."> 模式的字符串。但请注意,这个字符串内可能会有多余的空格,或者 URL 可能被单引号括起来,这些都会影响对字符串的查找。但是正则表达式为你提供了一个更加精确的语法,用于判断哪些字符序列是合法匹配的。
接下来,你将看到 Java API 中的正则表达式语法,以及如何使用正则表达式。
字符类(character class)是一组用方括号括起来的可选择的字符集合,例如 [Jj]、 [0-9]、[A-Za-z] 或 [^0-9]。在字符类中,字符 - 表示一个范围(Unicode 值位于两个边界之间的所有字符)。然而如果符号 - 是字符类中的第一个或最后一个字符,则表示其自身( - )。如果字符 ^ 是字符类中的第一个字符,则表示补集(除指定的字符外的所有字符)。
此外还有许多预定义的字符类(predefined character class),例如 \d(数字)或 \p{Sc}(Unicode 货币符号)。参见表 1。
字符 ^ 和 $ 分别匹配输入的开头和结尾。
如果需要使用. * + ? { | () [ \ ^ $ 这些符号的字面意思,那么需要在这些符号前加一个反斜杠。在字符类中,只需要转译 [ 和 \ ,前提是仔细处理 ] - ^ 的位置。例如, []^-] 是一个包含这 3 个字符的类。
或者也可以使用 \Q 和 \E 将字符串括起来。例如,\(\$0\.99\) 和 \Q($0.99)\E 都与字符串 ($0.99) 匹配。
如果字符串包含较多的正则表达式语法中的特殊字符,则可以通过调用 Parse.quote(str) 将它们全部转义。这样做会直接用 \Q 和 \E 把字符串括起来,而且可以处理好 str 内包含 \E 的特殊情况。
在第一种情况下,可以直接使用静态 matches() 方法:
如果需要多次使用同一个正则表达式,那么编译它能更好地提高运行效率。然后,为每个输入都创建一个 Matcher:
如果想要测试输入是否包含匹配,可以使用 find() 方法来替代:
更优雅的方式是,可以调用 results() 方法来获取一个 Stream<MatchResult>。MatchResult 接口就像 Matcher 类一样,有 group、start() 和 end() 方法。(事实上,Matcher 类实现了这个接口。)
下面展示了如何获取所有匹配的列表:
如果在文件中保存了数据,则可以使用 Scanner.findAll() 方法来获取一个 Stream< MatchResult>,这样就无须将先将内容读取到字符串中。可以将 Pattern 或模式字符串作为参数传递:
以下是一个正则表达式,它可以用分组的形式来处理数据中每个部分:
在匹配后,可以从匹配器中提取第 n 个分组:
分组按照它们的左括号排序,编号从 1 开始(分组 0 表示整个输入)。下列代码片段演示了如何将输入拆分:
这里我们对分组2并不感兴趣,因为它由重复产生的括号组成。为了更加清楚地表示,我们可以使用非捕获组来处理:
然后,就可以使用名称来获取商品了:
通过 start() 和 end() 方法,可以获得分组在输入中的位置:
如果有很多标记,则可以通过以下代码惰性地获取它们:
如果不关心预编译模式或惰性获取,那么可以使用String.split() 方法:
如果输入数据在文件中,那么需要使用扫描器:
如果不关注预编译模式,那么可以使用 String 类的 replaceAll() 方法:
替换字符串可以包含分组编号 $n 或名称 ${name},它们将被替换为对应的捕获组的内容:
可以使用 \ 来转义替换字符串中的 $ 和 \,也可以调用 Matcher.quoteReplacement() 方法来进行便捷处理:
如果想执行比按照分组匹配拼接更复杂的操作,那么可以使用替换函数来取代替换字符串。该函数接受 MatchResult 作为参数并生成字符串。例如,下面的例子中我们用大写字母来替换所有的至少包含 4 个字母的单词:
以下是一些常用标志:
最后两个标志不能在正则表达式内部指定。
例如,假设你想在 HTML 文件中查找超链接,需要查找 <a href="..."> 模式的字符串。但请注意,这个字符串内可能会有多余的空格,或者 URL 可能被单引号括起来,这些都会影响对字符串的查找。但是正则表达式为你提供了一个更加精确的语法,用于判断哪些字符序列是合法匹配的。
接下来,你将看到 Java API 中的正则表达式语法,以及如何使用正则表达式。
Java正则表达式语法
在正则表达式中,字符表示其自身,除非它是保留字符之一:
. * + ? { | () [ \ ^ $
例如:
- 正则表达式 Java 只能匹配字符串 Java;
- 符号 . 匹配任意单个字符。例如,.a.a 能够匹配 Java 和 data;
- 符号 * 则表示前面的构造可以重复出现 0 次或任意多次,符号 +则表示前面的构造重复 1 次或任意多次。后缀 ? 表示前面的构造是可选的(0 次或 1 次)。例如,be+s? 能够匹配 be、bee 和 bees。可以使用 { } 来指定其他类型的重复次数 (见表 1);
- 符号 | 表示选择:.(oo|ee)f 能够匹配 beef 或 woof。注意其中的圆括号,如果没有它们,那么.oo|eef 将表示 .oo 或者 eef。圆括号也可以用于分组。
字符类(character class)是一组用方括号括起来的可选择的字符集合,例如 [Jj]、 [0-9]、[A-Za-z] 或 [^0-9]。在字符类中,字符 - 表示一个范围(Unicode 值位于两个边界之间的所有字符)。然而如果符号 - 是字符类中的第一个或最后一个字符,则表示其自身( - )。如果字符 ^ 是字符类中的第一个字符,则表示补集(除指定的字符外的所有字符)。
此外还有许多预定义的字符类(predefined character class),例如 \d(数字)或 \p{Sc}(Unicode 货币符号)。参见表 1。
| 表达式 | 功能描述 | 范例 |
|---|---|---|
| 字符 | ||
| c, 除 .\*+?{|()[\<^$ 之外的字符 | 字符 c | J |
| . | 表示除行终止符之外的其他任意字符,或者在 DOTALL 标志设置后的任意字符 | |
| \x{p} | 十六进制码为 p 的 Unicode 码点 | \x{1D546} |
| \uhhhh, \xhh, \0o, \0oo, \0ooo | 具有给定十六进制或八进制值的 UTF-16 码元 | \uFEFF |
| \a, \e, \f, \n, \r, \t | 响铃符 (\x{7})、转义符 (\x{1B})、换页符 (\x{B})、换行符 (\x{A})、回车符 (\x{D})、制表符 (\x{9}) | \n |
| \cc 其中 c 在 [A-Z] 范围内,或者是 @[\]^_? 其中之一 | 字符 c 的控制字符 | \cH 是一个退格符 (\x{8}) |
| \c,c 不在 [A-Za-z0-9] 的范围内 | 字符 c | \\ |
| \Q ... \E | 引号内的所有内容 | \Q(...)\E 匹配字符串 (...) |
| 字符类 | ||
| [C1 C2 ...] , 其中 Ci 是 c-d 的多个字符,或者是字符类 | 任意由 C1、C2... 表示的字符 | [0-9+-] |
| [^...] | 字符类的补集 | [^\d\s] |
| [...&&...] | 字符类的交集 | [\p{L}&&[^A-Za-z]] |
| \p{...}, \P{...} | 预定义字符类(见表 2);它的补集 | \p{L} 匹配一个 Unicode 字母,同时 \pL 也匹配该字母,可以忽略单个字母情况下的括号 |
| \d, \D | 数字 ([0-9],或者在设置了 UNICODE_CHARACTER_CLASS 标记时表示 \p {Digit};它的补集 | \d+ 是一个数字序列 |
| \w, \W | 单词字符 [a-zA-Z0-9_],或者在设置了 UNICODE_CHARACTER_CLASS 标记时表示 Unicode 单词字符;它的补集 | |
| \s, \S | 空格 ([\n\r\t\f\x{B}],或者在设置了 UNICODE_CHARACTER_CLASS 标记时表示 \p{IsWhite_Space});它的补集 | \s*,\s* 是一个由可选的空格字符包围的逗号 |
| \h, \v, \H, \V | 水平空白字符、垂直空白字符、它们的补集 | |
| 序列和选择 | ||
| XY | 任意从 X 开始,后面跟随 Y 的字符串 | [1-9][0-9]* 是一个非 0 开头的正整数 |
| X|Y | 任意 X 或者 Y 的字符串 | http|ftp |
| 分组 | ||
| (X) | 捕获 X 的匹配 | '([^’]*)' 捕获被引用的文本 |
| \n | 第 n 组 | (['"]).*\1 匹配 'Fred' 或者 "Fred",但是不匹配 "Fred' |
| (?<name>X) | 捕获与给定名称匹配的 X | '(?<id>[A-Za-z0-9]+)' 捕获名称为 id 的匹配 |
| \k<name> | 给定名称的组 | \k<id> 匹配名称为 id 的分 |
| (?:X) | 仅使用括号,不捕获 X | 在 (?:http|ftp)://(.*) 中,在 :// 之后的匹配是 \1 |
| (?f1f2...:X), (?f1...-fk...:X) 其中 fi 在 [dimsux] 中 | 匹配但是不捕获给定标志开或关(在 - 之后) | (?i:jpe?g) 是一个不区分大小写的匹配 |
| 其他 (?.. .) | 查阅 Pattern API 手册 | |
| 量词 | ||
| X? | X 是可选的 | \+? 是可选的 + 号 |
| X*, X+ | 0 或多个 X;1 或多个 X | [1-9][0-9]+ 表示大于等于 10 的整数 |
| X{n}, X{n,}, X{m,n} | n 个 X;至少 n 个 X;m 到 n 个 X | [0-7]{1,3} 表示 1 到 3 位的八进制数 |
| Q?, Q 是一个量词表达式 | 勉强量词,先尝试最短匹配,再尝试最长匹配 | .*(<.+?>).* 捕获尖括号内的最短序列 |
| Q+, Q 是一个量词表达式 | 独占量词,在不回溯的情况下获取最长匹配 | '[^']*+' 匹配单引号内的字符串,并且在字符串中没有右侧单引号的情况下立即匹配失败 |
| 边界匹配 | ||
| ^ $ | 输入的开头和结尾(或者多行模式中的开头行和结尾行) | ^Java$ 匹配输入中的 Java 或者单行的 Java |
| \A \Z \z | 输入的开头、输入的结尾、输入的绝对结尾(在多行模式中不变) | |
| \b \B | 单词边界,非单词边界 | \bJava\b 匹配单词 Java |
| \R | Unicode 行分隔符 | |
| \G | 上一个匹配的结尾 | |
| 名字 | 功能描述 |
|---|---|
| posixClass | posixClass 是 Lower、Upper、Alpha、Digit、Alnum、Punct、Graph、Print、Cntrl、XDigit、Space、Blank、ASCII之一,它会依据UNICODE_CHARACTER_CLASS标志的值而被解释为POSIX或者Unicode类 |
| IsScript, sc=Script, script=Script | 能够被 Character.UnicodeScript.forName 接受的脚本 |
| InBlock, blk = Block, block = Block | 能够被 Character.UnicodeBlock.forName 接受的块 |
| Category, InCategory, gc=Category, general_category=Category | Unicode 通用类别中的单字母或者双字母的名字 |
| IsProperty | Property 是以下种类之一:Alphabetic、Ideographic、Letter、Lowercase、Uppercase、Titlecase、Punctuation、Control、White_Space、Digit、Hex_Digit、Join_Control、Noncharacter_Code_Point、Assigned |
| javaMethod | 调用 Character.isMethod() 方法(必须是没有被废弃的方法) |
字符 ^ 和 $ 分别匹配输入的开头和结尾。
如果需要使用. * + ? { | () [ \ ^ $ 这些符号的字面意思,那么需要在这些符号前加一个反斜杠。在字符类中,只需要转译 [ 和 \ ,前提是仔细处理 ] - ^ 的位置。例如, []^-] 是一个包含这 3 个字符的类。
或者也可以使用 \Q 和 \E 将字符串括起来。例如,\(\$0\.99\) 和 \Q($0.99)\E 都与字符串 ($0.99) 匹配。
如果字符串包含较多的正则表达式语法中的特殊字符,则可以通过调用 Parse.quote(str) 将它们全部转义。这样做会直接用 \Q 和 \E 把字符串括起来,而且可以处理好 str 内包含 \E 的特殊情况。
Java正则表达式检测匹配
通常使用正则表达式的方式有两种:检测字符串是否与正则表达式匹配,要么查找出字符串中正则表达式的所有匹配。在第一种情况下,可以直接使用静态 matches() 方法:
String regex = "[+-]?\\d+";
CharSequence input = ...;
if (Pattern.matches(regex, input)) {
...
}
如果需要多次使用同一个正则表达式,那么编译它能更好地提高运行效率。然后,为每个输入都创建一个 Matcher:
Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(input); if (matcher.matches()) ...如果匹配成功,那么能够获取匹配组的位置。
如果想要测试输入是否包含匹配,可以使用 find() 方法来替代:
if (matcher.find()) ...
Java正则表达式查找所有匹配
考虑正则表达式的另一个常见用法——在输入中查找所有匹配。为此,我们可以使用下面的循环:
String input = ...;
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
String match = matcher.group();
int matchStart = matcher.start();
int matchEnd = matcher.end();
...
}
通过这种方式,可以依次处理每个匹配。正如上面的代码片段所示,可以获取匹配的字符串及其在输入字符串中的位置。更优雅的方式是,可以调用 results() 方法来获取一个 Stream<MatchResult>。MatchResult 接口就像 Matcher 类一样,有 group、start() 和 end() 方法。(事实上,Matcher 类实现了这个接口。)
下面展示了如何获取所有匹配的列表:
List<String> matches = pattern.matcher(input) .results() .map(Matcher::group) .toList();
如果在文件中保存了数据,则可以使用 Scanner.findAll() 方法来获取一个 Stream< MatchResult>,这样就无须将先将内容读取到字符串中。可以将 Pattern 或模式字符串作为参数传递:
var in = new Scanner(path, StandardCharsets.UTF_8);
Stream<String> words = in.findAll("\\pL+")
.map(MatchResult::group);
Java正则表达式分组
通常可以使用分组来提取匹配的组件。例如,假设在发票中有一个行项目,包括商品名称、数量和单价等信息,如下所示:Blackwell Toaster USD29.95
以下是一个正则表达式,它可以用分组的形式来处理数据中每个部分:
(\p{Alnum}+(\s+\p{Alnum}+)*)\s+([A-Z]{3})([0-9.]*)
在匹配后,可以从匹配器中提取第 n 个分组:
String contents = matcher.group(n);
分组按照它们的左括号排序,编号从 1 开始(分组 0 表示整个输入)。下列代码片段演示了如何将输入拆分:
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
item = matcher.group(1);
currency = matcher.group(3);
price = matcher.group(4);
}
这里我们对分组2并不感兴趣,因为它由重复产生的括号组成。为了更加清楚地表示,我们可以使用非捕获组来处理:
?:或者按名字捕获:
<item><currency><price>
然后,就可以使用名称来获取商品了:
item = matcher.group("item");
通过 start() 和 end() 方法,可以获得分组在输入中的位置:
int itemStart = matcher.start("item");
int itemEnd = matcher.end("item");
注意,如果分组中有重复,比如上面的例子中的 (\s+\p{Alnum}+)*,那么将不能得到它的所有匹配。group() 方法只产生最后一个匹配,这基本是无意义的。这时,需要用另一个分组来捕获整个表达式。按名称获取分组只适用于 Matcher,而不适用于 MatchResult。
Java正则表达式按分隔符拆分
有时,你希望按照匹配的分隔符拆分输入,并保持其他内容不变。Pattern.split() 方法将会自动完成这项工作。你可以获得一组去掉分隔符的字符串数组:
String input = ...;
Pattern commas = Pattern.compile("\\s*,\\s*");
String[] tokens = commas.split(input);
// "1, 2, 3" turns into ["1", "2", "3"]
如果有很多标记,则可以通过以下代码惰性地获取它们:
Stream<String> tokens = commas.splitAsStream(input);
如果不关心预编译模式或惰性获取,那么可以使用String.split() 方法:
String[] tokens = input.split("\\s*,\\s*");
如果输入数据在文件中,那么需要使用扫描器:
var in = new Scanner(path, StandardCharsets.UTF_8);
in.useDelimiter("\\s*,\\s*");
Stream<String> tokens = in.tokens();
Java正则表达式替换匹配
如果希望用字符串来替换正则表达式的所有匹配,那么可以调用匹配器的 replaceAll() 方法:
Matcher matcher = commas.matcher(input);
String result = matcher.replaceAll(",");
// Normalizes the commas
如果不关注预编译模式,那么可以使用 String 类的 replaceAll() 方法:
String result = input.replaceAll("\\s*,\\s*", ",");
替换字符串可以包含分组编号 $n 或名称 ${name},它们将被替换为对应的捕获组的内容:
String result = "3:45".replaceAll(
"(\\d{1,2}):(?<minutes>\\d{2})",
"$1 hours and ${minutes} minutes");
// Sets result to "3 hours and 45 minutes"
可以使用 \ 来转义替换字符串中的 $ 和 \,也可以调用 Matcher.quoteReplacement() 方法来进行便捷处理:
matcher.replaceAll(Matcher.quoteReplacement(str))
如果想执行比按照分组匹配拼接更复杂的操作,那么可以使用替换函数来取代替换字符串。该函数接受 MatchResult 作为参数并生成字符串。例如,下面的例子中我们用大写字母来替换所有的至少包含 4 个字母的单词:
String result = Pattern.compile("\\pL{4,}")
.matcher("Mary had a little lamb")
.replaceAll(m -> m.group().toUpperCase());
// Yields "MARY had a LITTLE LAMB"
replaceFirst() 方法将只替换模式匹配中第一次出现的匹配。Java正则表达式的标志
标志(flag)可以改变正则表达式的行为。可以在编译模式时指定它们:Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS);
以下是一些常用标志:
- Pattern.CASE_INSENSTIVE 或 i:匹配字符时忽略字母大小写。默认情况下,此标志仅考虑 US ASCII 字符。
- Pattern.UNICODE_CASE 或 u:当与 CASE_INSENSITIVE 组合使用时,使用 Unicode 字母大小写进行匹配。
- Pattern.UNICODE_CHARACTER_CLASS 或 U:使用 Unicode 字符类代替 POSIX。该标志隐含了UNICODE_CASE。
- Pattern.MULTILINE 或 m:使 ^ 和 $ 匹配行的开头和结尾,不是整个输入的开头和结果。
- Pattern.UNIX_LINES 或 d:当在多行模式中匹配 ^ 和 $ 时,仅 '\n' 表示行终止符。
- Pattern.DOTALL 或 s:使 . 符号匹配所有字符,包括行终止符。
- Pattern.COMMENTS 或 x:空白字符和注释(从 # 到行末尾)都会被忽略。
- Pattern.LITERAL:除大小写字母外,该模式将被逐字采纳,并且必须精确匹配。
- Pattern.CANON_EQ:考虑 Unicode 字符的规范等效性。例如,u 后跟 ¨ (分音符)匹配 ü。
最后两个标志不能在正则表达式内部指定。
ICP备案:
公安联网备案: