1. 概述

正则表达式(Regular Expressions)在 Java 中广泛应用于文本处理,比如输入校验、日志解析、关键词提取等场景。其中,一个常见需求是:统计某段文本中正则表达式匹配的次数

本文将详细介绍如何使用 Java 实现这一功能,并对比 Java 8 与 Java 9+ 的不同实现方式,帮你避免踩坑,写出更简洁高效的代码。


2. 使用场景

我们以一个实际场景为例:统计一段文本中合法邮箱地址出现的次数

为了识别邮箱,我们使用一个简化的正则表达式(仅用于演示):

([a-z0-9_.-]+)@([a-z0-9_.-]+[a-z])

⚠️ 注意:真实的邮箱校验正则非常复杂(RFC 5322),这里只是为了演示目的做了极大简化,实际项目中建议使用 Apache Commons Validator 或更严谨的模式。

我们将该正则封装为一个 Pattern 常量:

Pattern EMAIL_ADDRESS_PATTERN = 
  Pattern.compile("([a-z0-9_.-]+)@([a-z0-9_.-]+[a-z])");

测试文本包含三个邮箱:

String TEXT_CONTAINING_EMAIL_ADDRESSES = 
  "You can contact me through user@example.com, admin@site.org, and test@domain.net";

目标是正确识别出 3 个匹配项


3. Java 8 及更早版本的实现方式

在 Java 8 及之前,最直接的方式是使用 Matcher.find() 方法配合循环遍历。

✅ 核心思路:
find() 方法会从上一次匹配结束位置继续查找下一个匹配项,直到文本末尾。

示例代码如下:

Matcher countEmailMatcher = EMAIL_ADDRESS_PATTERN.matcher(TEXT_CONTAINING_EMAIL_ADDRESSES);

int count = 0;
while (countEmailMatcher.find()) {
    count++;
}

assertEquals(3, count);

⚠️ 注意事项

  • find() 不会重置匹配器状态,而是从上次匹配的结尾继续搜索。
  • 因此,它无法匹配重叠(overlapping)的子串

举个例子:

String OVERLAPPING_EMAIL_ADDRESSES = "Try to contact us at user@@baeldung.com, admin@site.com.";

在这个字符串中,user@@baeldung.com 实际上不是一个合法邮箱,但我们的正则会匹配到:

  1. user@(前半部分) ❌
  2. @baeldung.com(跳过第一个 @ 后的合法部分)❌
  3. admin@site.com

但由于 find() 是非重叠查找,它只会找到两个有效匹配:

  • 第一次匹配 user@(假设前面有合法用户名)
  • 第二次匹配 admin@site.com

而中间的 @baeldung.com 因为紧接在第一个匹配之后,不会被重新检查。

最终结果:

Matcher countOverlappingEmailsMatcher = EMAIL_ADDRESS_PATTERN.matcher(OVERLAPPING_EMAIL_ADDRESSES);

int count = 0;
while (countOverlappingEmailsMatcher.find()) {
    count++;
}

assertEquals(2, count); // 实际只找到两个

match regex

如图所示,匹配过程是线性推进的,跳过了潜在的重叠区域。


4. Java 9 及更高版本的实现方式

从 Java 9 开始,Matcher 类新增了一个非常实用的方法:results()

results() 返回一个 Stream<MatchResult>,表示所有匹配结果的流,我们可以直接调用 count() 统计数量。

代码变得极其简洁:

long count = EMAIL_ADDRESS_PATTERN
    .matcher(TEXT_CONTAINING_EMAIL_ADDRESSES)
    .results()
    .count();

assertEquals(3, count);

✅ 优势

  • 无需手动写循环
  • 函数式风格,代码更清晰
  • 可与其他 Stream 操作组合(如过滤、去重等)

⚠️ 注意点

  • find() 一样,results() 也是非重叠匹配
  • 匹配位置依然从上一次结束处开始,不支持重叠子串检测

所以对于上面的重叠邮箱例子,结果仍然是 2:

long count = EMAIL_ADDRESS_PATTERN
    .matcher(OVERLAPPING_EMAIL_ADDRESSES)
    .results()
    .count();

assertEquals(2, count);

5. 总结

方式 Java 版本 是否推荐 说明
find() + while 循环 Java 8 及以下 兼容性好,逻辑清晰
results().count() Java 9+ ✅✅✅ 更现代、简洁,推荐新项目使用

📌 关键要点:

  • 正则匹配默认是非重叠的,不要指望靠 find()results() 检测重叠模式
  • 如果真有重叠匹配需求(极少见),需要手动控制索引或使用其他工具库
  • 邮箱正则不要自己造轮子,生产环境建议使用成熟方案(如 javax.mail.internet.InternetAddress 校验)

所有示例代码已上传至 GitHub:

👉 https://github.com/baeldung/java-tutorials/tree/master/core-java-regex


原始标题:How to Count the Number of Matches for a Regex?