杰瑞科技汇

Java正则如何精准匹配特定字符串?

核心概念

在 Java 中,正则表达式主要通过 java.util.regex 包中的两个类来实现:

  1. Pattern: 一个正则表达式编译后的表示形式,它是一个不可变的类,你首先需要将你的正则表达式字符串编译成一个 Pattern 对象。
  2. Matcher: 一个引擎,它对输入字符串进行解释和匹配操作,它是由 Pattern 对象创建的,并提供了各种方法来执行匹配、查找和替换操作。

简单流程: 正则表达式字符串 -> Pattern.compile() -> Pattern 对象 -> pattern.matcher() -> Matcher 对象 -> matcher.find() / matcher.matches() / ... -> boolean匹配到的结果


常用匹配方法

Matcher 类提供了三种主要的匹配方法,它们的行为各不相同,非常重要:

方法 描述 示例 (正则: "\\d+", 字符串: "abc123def456")
matches() 整个字符串必须完全匹配正则表达式模式。 返回 false,因为字符串 "abc123def456" 不完全是一个或多个数字。
lookingAt() 从字符串的开头开始匹配,但不需要匹配整个字符串。 返回 true,因为开头部分 "123" 匹配 \\d+
find() 在字符串中查找与模式匹配的子序列,可以多次调用。 第一次调用返回 true,找到 "123",第二次调用返回 true,找到 "456",第三次调用返回 false

完整代码示例

下面是一个完整的示例,演示了这三种核心方法以及其他常用功能。

import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexExample {
    public static void main(String[] args) {
        // 1. 定义要匹配的字符串
        String text = "Hello, my phone number is 123-456-7890, and my email is test.user@example.com.";
        // 2. 定义正则表达式
        // \d 表示任意数字 [0-9]
        // {3} 表示前面的元素(这里是\d)恰好出现3次
        // - 是一个普通字符,需要转义为 \-
        // \. 匹配点号,因为 . 在正则中有特殊含义(匹配任意字符),所以需要转义
        // + 表示前面的元素(这里是 [a-zA-Z0-9])出现一次或多次
        // * 表示前面的元素(这里是 \.)出现零次或多次
        String phoneRegex = "\\d{3}-\\d{3}-\\d{4}";
        String emailRegex = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}";
        // 3. 编译正则表达式,创建 Pattern 对象
        // 好的做法是将编译好的 Pattern 缓存起来(例如使用 static final),因为编译是开销较大的操作
        Pattern phonePattern = Pattern.compile(phoneRegex);
        Pattern emailPattern = Pattern.compile(emailRegex);
        // --- 核心匹配方法演示 ---
        // a) matches() - 整个字符串匹配
        System.out.println("--- 使用 matches() ---");
        boolean isPhoneFullMatch = phonePattern.matcher("123-456-7890").matches();
        System.out.println("'123-456-7890' 是否完全匹配电话号码格式? " + isPhoneFullMatch); // true
        boolean isTextPhoneMatch = phonePattern.matcher(text).matches();
        System.out.println("整个 text 是否完全匹配电话号码格式? " + isTextPhoneMatch); // false
        System.out.println("------------------------------------\n");
        // b) lookingAt() - 从开头匹配
        System.out.println("--- 使用 lookingAt() ---");
        boolean isTextStartsWithPhone = phonePattern.matcher(text).lookingAt();
        System.out.println("text 是否以电话号码格式开头? " + isTextStartsWithPhone); // false
        System.out.println("------------------------------------\n");
        // c) find() - 查找匹配的子串
        System.out.println("--- 使用 find() ---");
        // 创建 Matcher 对象
        Matcher phoneMatcher = phonePattern.matcher(text);
        System.out.println("在 text 中查找所有电话号码:");
        while (phoneMatcher.find()) {
            // group() 方法返回最近一次匹配到的子串
            System.out.println("找到电话号码: " + phoneMatcher.group());
        }
        System.out.println("------------------------------------\n");
        // 查找所有邮箱
        Matcher emailMatcher = emailPattern.matcher(text);
        System.out.println("在 text 中查找所有邮箱:");
        while (emailMatcher.find()) {
            System.out.println("找到邮箱: " + emailMatcher.group());
        }
        System.out.println("------------------------------------\n");
        // --- 其他常用功能 ---
        // d) 获取匹配的详细信息
        System.out.println("--- 获取匹配的详细信息 ---");
        String sampleText = "Order 123 is confirmed.";
        Matcher orderMatcher = Pattern.compile("(Order) (\\d+)").matcher(sampleText);
        if (orderMatcher.find()) {
            System.out.println("完整匹配: " + orderMatcher.group(0)); // group(0) 是整个匹配
            System.out.println("第一个捕获组 (Order): " + orderMatcher.group(1)); // group(1) 是第一个括号里的内容
            System.out.println("第二个捕获组 (123): " + orderMatcher.group(2)); // group(2) 是第二个括号里的内容
            // start() 和 end() 返回匹配的起始和结束索引(end 是 exclusive 的)
            System.out.println("匹配的起始位置: " + orderMatcher.start());
            System.out.println("匹配的结束位置: " + orderMatcher.end());
        }
        System.out.println("------------------------------------\n");
        // e) 替换匹配的文本
        System.out.println("--- 使用 replaceAll() ---");
        String anonymizedText = text.replaceAll("\\d{3}-\\d{3}-\\d{4}", "[PHONE-NUMBER]");
        System.out.println("替换后的文本: " + anonymizedText);
        String textWithUnderscores = text.replaceAll("\\s", "_"); // \s 匹配任何空白字符
        System.out.println("空格替换为下划线: " + textWithUnderscores);
        System.out.println("------------------------------------\n");
        // f) 分割字符串
        System.out.println("--- 使用 split() ---");
        String csvData = "apple,banana,cherry,date";
        String[] fruits = Pattern.compile(",").split(csvData);
        for (String fruit : fruits) {
            System.out.println("水果: " + fruit);
        }
        System.out.println("------------------------------------\n");
    }
}

常用正则表达式元字符

为了让你能写出自己的正则表达式,这里是一些最常用的元字符:

元字符 描述 示例
匹配除换行符以外的任意单个字符 c.t 可以匹配 "cat", "cot", "c8t"
匹配前面的元素零次或多次 go*gle 可以匹配 "ggle", "gogle", "google"
匹配前面的元素一次或多次 go+gle 可以匹配 "gogle", "google" (但不能匹配 "ggle")
匹配前面的元素零次或一次 colou?r 可以匹配 "color" 和 "colour"
{n} 匹配前面的元素恰好 n 次 \d{3} 匹配3个数字
{n,} 匹配前面的元素至少 n 次 \d{2,} 匹配2个或更多数字
{n,m} 匹配前面的元素至少 n 次,但不超过 m 次 \d{2,4} 匹配2到4个数字
[] 字符类,匹配方括号内的任意一个字符 [abc] 匹配 "a", "b", 或 "c"
[^] 否定字符类,匹配不在方括号内的任意字符 [^0-9] 匹配任意非数字字符
[a-z] 匹配指定范围内的任意字符 [a-z] 匹配任意小写字母
\d 匹配数字,等同于 [0-9] \d 匹配 "1", "2", "9"
\D 匹配非数字字符,等同于 [^0-9] \D 匹配 "a", " ", "#"
\w 匹配单词字符,等同于 [a-zA-Z0-9_] \w 匹配 "a", "Z", "5", "_"
\W 匹配非单词字符,等同于 [^a-zA-Z0-9_] \W 匹配 " ", "@", "#"
\s 匹配空白字符(空格, tab, 换行等) \s 匹配 " ", "\t", "\n"
\S 匹配非空白字符 \S 匹配 "a", "1", "@"
^ 匹配字符串的开头 ^Hello 匹配 "Hello world" 的开头
匹配字符串的 world$ 匹配 "Hello world" 的结尾
捕获组,将括号内的表达式作为一个整体 (ab)+ 匹配 "ab", "abab"
\ 转义字符,用于匹配元字符本身 \. 匹配一个真实的点号
\| ,匹配两个或多个表达式之一 cat\|dog 匹配 "cat" 或 "dog"

性能提示

  1. 预编译 Pattern: Pattern.compile() 是一个相对耗时的操作,如果你的同一个正则表达式要在循环或频繁调用的代码中使用,请务必将其编译为 static finalPattern 对象,并重复使用它。

    // 推荐
    private static final Pattern PHONE_PATTERN = Pattern.compile("\\d{3}-\\d{3}-\\d{4}");
    // 不推荐 (每次调用都重新编译)
    public boolean isPhone(String s) {
        return Pattern.compile("\\d{3}-\\d{3}-\\d{4}").matcher(s).matches();
    }
  2. 避免贪婪匹配: �情况下,量词(如 , , )是贪婪的,它们会匹配尽可能多的字符,如果你只需要匹配到第一个结束标签,可以使用懒惰(或非贪婪)模式,即在量词后加上 。

    • 贪婪: <div>.*</div> 会匹配从第一个 <div> 到最后一个 </div> 之间的所有内容。
    • 懒惰: <div>.*?</div> 会匹配从第一个 <div> 到第一个 </div> 之间的内容。

希望这份详细的指南能帮助你掌握 Java 正则表达式的使用!

分享:
扫描分享到社交APP
上一篇
下一篇