杰瑞科技汇

Java正则Pattern如何高效匹配与分组?

java.util.regex.Pattern 是 Java 中用于编译正则表达式核心类,它代表一个经过编译的模式,可以被用来创建 Matcher 对象,从而在字符串中进行匹配操作。

Java正则Pattern如何高效匹配与分组?-图1
(图片来源网络,侵删)

理解 Pattern 的工作流程是掌握 Java 正则表达式的关键:

  1. 编译: 将一个正则表达式字符串(如 "\d+")编译成一个 Pattern 对象,这个过程相对耗时,所以如果需要多次使用同一个正则表达式,最好只编译一次并复用 Pattern 对象。
  2. 匹配: 使用 Pattern 对象创建一个 Matcher 对象。
  3. 操作: 通过 Matcher 对象在目标字符串上进行查找、替换、分割等操作。

核心概念

Pattern

Pattern 类是一个不可变的(final)类,它包含了已编译的正则表达式。

  • 如何创建? 不能使用 new 关键字直接实例化,必须通过其静态工厂方法 compile(String regex) 来创建。
    // 编译正则表达式 \d+ (匹配一个或多个数字)
    Pattern pattern = Pattern.compile("\\d+");

Matcher

Matcher 类是对输入字符串执行匹配操作的引擎,它提供了各种查找方法。

  • 如何创建? 通过 Pattern 对象的 matcher(CharSequence input) 方法创建。
    String input = "我的电话是13812345678,座机是010-12345678。";
    Matcher matcher = pattern.matcher(input);

常用方法

Pattern 类的常用方法

方法 描述 示例
static Pattern compile(String regex) 编译给定的正则表达式字符串,返回一个 Pattern 对象。 Pattern p = Pattern.compile("\\d+");
static Pattern compile(String regex, int flags) 编译正则表达式,并指定标志(如大小写不敏感等)。 Pattern p = Pattern.compile("abc", Pattern.CASE_INSENSITIVE);
String pattern() 返回原始编译时的正则表达式字符串。 p.pattern(); // 返回 "\\d+"
String[] split(CharSequence input) 根据模式分割输入字符串,返回字符串数组。 p.split("a1b2c3"); // 返回 ["a", "b", "c"]
String[] split(CharSequence input, int limit) 根据模式分割输入字符串,limit 参数控制分割的次数。 p.split("a1b2c3", 2); // 返回 ["a", "b2c3"]

Matcher 类的常用方法

方法 描述 示例
boolean find() 尝试在字符串中查找下一个匹配的子序列。这是最常用的方法。 while (m.find()) { ... }
boolean find(int start) 重置匹配器,并尝试从指定的索引位置开始查找。 m.find(10);
boolean matches() 尝试将整个区域与模式匹配。要求整个字符串都必须匹配。 Pattern p = Pattern.compile("\\d+"); p.matcher("123").matches(); // true
p.matcher("a123").matches(); // false
boolean lookingAt() 尝试将区域开头与模式匹配。只要求字符串开头匹配即可。 Pattern p = Pattern.compile("\\d+"); p.matcher("123abc").lookingAt(); // true
p.matcher("abc123").lookingAt(); // false
String group() 返回当前匹配到的子串。 m.group(); // 返回 "13812345678"
int start() 返回当前匹配子串的起始索引。 m.start(); // 返回 4
int end() 返回当前匹配子串的结束索引(exclusive,即下一个字符的索引)。 m.end(); // 返回 16
int groupCount() 返回匹配器模式中的捕获组数量。0 表示整个模式。 m.groupCount();
String group(int group) 返回指定捕获组匹配到的子串。 Pattern p = Pattern.compile("(\\d{3})(\\d{4})(\\d{4})");
m.group(1); // 返回 "138"

常用正则表达式元字符

这里列出一些最常用的元字符,它们是构建正则表达式的基础。

Java正则Pattern如何高效匹配与分组?-图2
(图片来源网络,侵删)
元字符 描述 示例
匹配除换行符以外的任意单个字符 a.c 匹配 "abc", "aac", "a c"
^ 匹配字符串开头的位置。 ^abc 匹配 "abc123",不匹配 "123abc"
匹配字符串结尾的位置。 abc$ 匹配 "123abc",不匹配 "abc123"
匹配前面的子表达式零次或多次 a* 匹配 "", "a", "aa", "aaa"
匹配前面的子表达式一次或多次 a+ 匹配 "a", "aa", "aaa",不匹配 ""
匹配前面的子表达式零次或一次 a? 匹配 "", "a"
{n} 精确匹配前面的子表达式 n 次。 \d{3} 匹配3个连续的数字
{n,} 匹配前面的子表达式至少 n 次。 \d{2,} 匹配2个或更多连续的数字
{n,m} 匹配前面的子表达式至少 n 次,但不超过 m 次。 \d{2,4} 匹配2到4个连续的数字
当跟在 , , , , 后面时,表示非贪婪模式(匹配尽可能少的字符)。 "<.*>" 贪婪模式会匹配 <div>hello</div>
"<.*?>" 非贪婪模式会匹配 <div></div>
\d 匹配一个数字,等价于 [0-9] \d 匹配 '1', '2', '9'
\D 匹配一个非数字字符,等价于 [^0-9] \D 匹配 'a', ' ', '#'
\w 匹配单词字符(字母、数字、下划线),等价于 [a-zA-Z0-9_] \w 匹配 'a', 'B', '1', '_'
\W 匹配非单词字符,等价于 [^a-zA-Z0-9_] \W 匹配 '!', '@', ' '
\s 匹配任何空白字符(空格、制表符、换行符等)。 \s 匹配 ' ', '\t', '\n'
\S 匹配任何非空白字符 \S 匹配 'a', '1', '!'
[...] 字符集,匹配方括号内的任意一个字符。 [abc] 匹配 'a', 'b', 或 'c'
[^...] 否定字符集,匹配不在方括号内的任意一个字符。 [^abc] 匹配 'd', '1', '!' (只要不是a,b,c)
捕获组,将括号内的子表达式作为一个整体,并记住匹配的内容。 (\d{3})-(\d{4}) 可以分别捕获区号和号码
\ 转义字符,用于转义特殊字符,使其字面化。 \\ 匹配反斜杠 \
\. 匹配点

完整代码示例

下面是一个综合示例,演示了 PatternMatcher 的多种用法。

import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexExample {
    public static void main(String[] args) {
        String text = "学习Java正则表达式,版本有Java 8, Java 11, Java 17,我的邮箱是test.user+alias@example.com,电话是138-1234-5678。";
        // 1. 编译正则表达式
        // 目标:查找所有 "Java" 后面跟着一个空格和一个数字的版本号
        Pattern versionPattern = Pattern.compile("Java (\\d+)");
        // 2. 创建 Matcher 对象
        Matcher versionMatcher = versionPattern.matcher(text);
        System.out.println("--- 查找所有Java版本号 ---");
        // 3. 使用 find() 查找所有匹配项
        while (versionMatcher.find()) {
            // group(0) 是整个匹配的子串
            // group(1) 是第一个捕获组的内容
            System.out.println("找到版本: " + versionMatcher.group(0) + 
                               ", 具体数字: " + versionMatcher.group(1) +
                               ", 起始位置: " + versionMatcher.start());
        }
        System.out.println("\n--- 验证整个字符串是否匹配邮箱格式 ---");
        // 目标:验证一个字符串是否是有效的邮箱地址
        String email1 = "test@example.com";
        String email2 = "invalid-email";
        String emailRegex = "^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$";
        Pattern emailPattern = Pattern.compile(emailRegex);
        System.out.println("验证 '" + email1 + "': " + emailPattern.matcher(email1).matches());
        System.out.println("验证 '" + email2 + "': " + emailPattern.matcher(email2).matches());
        System.out.println("\n--- 提取电话号码 ---");
        // 目标:提取符合 "XXX-XXXX-XXXX" 格式的电话号码
        String phoneRegex = "(\\d{3})-(\\d{4})-(\\d{4})";
        Pattern phonePattern = Pattern.compile(phoneRegex);
        Matcher phoneMatcher = phonePattern.matcher(text);
        if (phoneMatcher.find()) {
            System.out.println("找到电话号码: " + phoneMatcher.group(0));
            System.out.println("区号: " + phoneMatcher.group(1));
            System.out.println("前缀: " + phoneMatcher.group(2));
            System.out.println("后缀: " + phoneMatcher.group(3));
        }
        System.out.println("\n--- 使用 split 方法分割字符串 ---");
        // 目标:用数字作为分隔符来分割字符串
        String splitText = "abc123def456ghi";
        Pattern splitPattern = Pattern.compile("\\d+");
        String[] parts = splitPattern.split(splitText);
        for (String part : parts) {
            System.out.println("分割部分: " + part);
        }
    }
}

Pattern 的标志

Pattern.compile(String regex, int flags) 方法允许你传入标志来修改匹配行为,这些标志是 Pattern 类的静态常量。

标志 描述
CASE_INSENSITIVE 2 匹配时不区分大小写。
MULTILINE 8 影响 ^ 和 的行为,在此模式下,^ 匹配行的开头, 匹配行的结尾。
DOTALL 32 让 匹配包括换行符在内的所有字符。
UNICODE_CASE 64 CASE_INSENSITIVE 一起使用,进行 Unicode 大小写不敏感的匹配。
UNIX_LINES 1 在多行模式下,只将 \n 识别为行终止符。
CANON_EQ 128 考虑 Unicode 字符的规范等价性。

示例:使用 MULTILINE 标志

String multilineText = "第一行\n第二行\n第三行";
// 不使用 MULTILINE 标志
Pattern p1 = Pattern.compile("^第二行$");
System.out.println(p1.matcher(multilineText).find()); // false
// 使用 MULTILINE 标志
Pattern p2 = Pattern.compile("^第二行$", Pattern.MULTILINE);
System.out.println(p2.matcher(multilineText).find()); // true

最佳实践

  1. 预编译正则表达式:如果你的正则表达式会被多次使用(例如在循环中),一定要先编译成 Pattern 对象并复用,而不是每次都调用 String.matches() 等方法,因为后者每次都会隐式地编译新的 Pattern,效率较低。
  2. 使用原始字符串字面量:在 Java 中,反斜杠 \ 是一个转义字符,为了在正则表达式中使用 \d,你需要在字符串字面量中写成 "\\d",这非常容易出错,一个更好的方法是使用 Pattern 类的 quote() 方法来转义任何字面字符串,或者干脆在代码中处理好。
  3. 区分 matches(), lookingAt(), 和 find()
    • matches(): 整个字符串必须完全匹配。
    • lookingAt(): 字符串开头部分匹配即可。
    • find(): 在字符串中查找任意位置的子串匹配,最灵活,也最常用。
  4. 小心贪婪与非贪婪:默认情况下,量词(, , , )是贪婪的,会匹配尽可能多的字符,如果这不符合你的预期(例如解析HTML/XML标签),记得在量词后加上 使其变为非贪婪模式。
Java正则Pattern如何高效匹配与分组?-图3
(图片来源网络,侵删)
分享:
扫描分享到社交APP
上一篇
下一篇