Appearance
Java 基础:正则
1. 匹配字符串
正则表达式的最简单用法就是测试某个特定的字符串是否与它匹配:
Java
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) ...1
2
3
2
3
这个匹配器的输入可以是任何实现了 CharSequence 接口的类的对象,例如 String、StringBuilder 和 CharBuffer。
在编译模式时,还可以设置一个或多个标志,例如:
Java
Pattern pattern = Pattern.compile(expression, Pattern.CASE_INSENSITIVE + Pattern.UNICODE_CASE);也可以在模式中指定它们:
Java
String regex = "(?iU:expression)";Pattern.CASE_INSENSITIVEori:匹配字符时忽略字母的大小写,默认情况下,这个标志只考虑 US ASCII 字符;Pattern.UNICODE_CASEoru:当与CASE_INSENSITIVE组合使用时,用 Unicode 字母的大小写来匹配;Pattern.UNICODE_CHARACTER_CLASSorU:选择 Unicode 字符类代替 POSIX,其中蕴含了UNICODE_CASE;Pattern.MULTILINEorm:^和$匹配行的开头和结尾,而不是整个输入的开头和结尾;Pattern.UNIX_LINESord:在多行模式中匹配^和$时,只有\n被识别成行终止符;Pattern.DOTALLors:当使用这个标志时,.符号匹配所有字符,包括行终止符;Pattern.COMMENTSorx:空白字符和注释(从#到行末尾)将被忽略;Pattern.LITERAL:该模式将被逐字地采纳,必须精确匹配,因字母大小写而造成的差异除外;Pattern.CANON_EQ:考虑 Unicode 字符规范的等价性,例如,u后面跟随¨(分音符号)匹配ü;
Note:最后两个标志不能在正则表达式内部指定。
如果想要在集合或流中匹配元素,那么可以将模式转换为谓词(函数式接口):
Java
Stream<String> strings = ...;
Stream<String> result = strings.filter(pattern.asPredicate());1
2
2
如果正则表达式包含群组,那么 Matcher 对象可以获取群组的边界:
Java
int start(int groupIndex)
int start(String groupName)
int end(int groupIndex)
int end(String groupName)1
2
3
4
2
3
4
可以直接通过调用下面的方法抽取匹配的字符串:
Java
String group(int groupIndex)
String group(String groupName)1
2
2
2. 找出多个匹配
如果想找出输入中一个或多个匹配的子字符串。这时可以使用 Matcher 类的 find 方法来查找匹配内容,如果返回 true,再使用 start 和 end 方法来查找匹配的内容,或使用不带参数的 group 方法来获取匹配的字符串。
Java
while (matcher.find()) {
int start = matcher.start();
int end = matcher.end();
String match = input.group();
...
}1
2
3
4
5
6
2
3
4
5
6
更优雅的方法是调用 results 方法来获取一个 Stream<MatchResult>。MatchResult 接口有 group、start 和 end 方法,就像 Matcher 一样(事实上,Matcher 类也实现了 MatchResult 这个接口)。下面展示了如何获取所有匹配的列表:
Java
List<String> matches = pattern.matcher(input)
.results()
.map(MatchResult::group)
.collect(Collectors.toList());1
2
3
4
2
3
4
如果要处理的是文件中的数据,那么可以使用 Scanner.findAll 方法来获取一个 Stream<MatchResult>,这样就无须先将内容读取到一个字符串中。可以给 Scanner 传递一个 Pattern 或一个模式字符串:
Java
var in = new Scanner(path, StandardCharsets.UTF_8);
Stream<String> words = in.findAll("\\pL+").map(MatchResult::group);1
2
2
3. 用分隔符来分割
可以使用 Pattern.split 方法,来将输入按匹配的分隔符断开,并得到一个字符串数组:
Java
String input = ...;
Pattern commas = Pattern.compile("\\s*,\\s*");
String[] tokens = commas.split(input); // "1, 2, 3" turns into ["1", "2", "3"]1
2
3
2
3
还可以惰性地获取它们:
Java
Stream<String> tokens = commas.splitAsStream(input);如果不关心预编译模式和惰性获取,那么可以使用 String.split 方法:
Java
String[] tokens = input.split("\\s*,\\s*");对于文件可以使用 Scanner:
Java
var in = new Scanner(path, StandardCharsets.UTF_8);
in.useDelimiter("\\s*,\\s*");
Stream<String> tokens = in.tokens();1
2
3
2
3
4. 替换匹配
Matcher 类的 replaceAll 方法将正则表达式匹配的所有地方都用替换字符串来替换:
Java
Pattern pattern = Pattern.compile("[0-9]+");
Matcher matcher = pattern.matcher(input);
String output = matcher.replaceAll("#");1
2
3
2
3
替换字符串可以包含对模式中群组的引用:$n 表示替换成第 n 个群组,${name} 被替换为具有给定名字的组,因此我们需要用 \$ 来表示在替换文本中包含一个 $ 字符。
如果字符串中包含 $ 和 \,但是又不希望它们被解释成群组的替换符,那么就可以调用 matcher.replaceAll(Matcher.quoteReplacement(str))。
如果想要执行比按照群组匹配拼接更复杂的操作,可以提供一个替换函数而不是替换字符串:
Java
String result = Pattern.compile("\\pL{4,}")
.matcher("Mary had a little lamb")
.replaceAll(m -> m.group().toUpperCase());
// Yields "MARY had a LITTLE LAMB"1
2
3
4
2
3
4
replaceFirst 方法将只替换模式的第一次出现。