全面解析 Java 正则表达式:基础、语法与应用 – wiki大全

全面解析 Java 正则表达式:基础、语法与应用

Java 正则表达式(Regular Expressions,简称 regex 或 regexp)是一种强大的字符串处理工具,它允许我们通过模式匹配来搜索、替换、提取和验证字符串。无论是数据校验、日志分析还是文本处理,正则表达式都是 Java 开发者不可或缺的技能。本文将从基础概念、核心语法到实际应用,全面解析 Java 正则表达式。

一、正则表达式基础

1. 什么是正则表达式?

正则表达式是一个描述字符模式的对象。这些模式被用来匹配文本中的一系列字符串。在 Java 中,正则表达式通过 java.util.regex 包提供支持,主要包含三个核心类:

  • Pattern: 表示一个编译好的正则表达式。它是正则表达式的编译表示。
  • Matcher: 是一个引擎,它通过解释 Pattern 来对输入字符串执行匹配操作。
  • PatternSyntaxException: 非强制性异常,用于指示正则表达式模式中的语法错误。

2. 基本匹配

最简单的正则表达式就是普通字符。例如,正则表达式 a 会匹配字符串中所有出现的字母 “a”。

  • abc:匹配精确的字符串 “abc”。

二、正则表达式核心语法

Java 正则表达式的语法与 Perl 语言高度兼容,下面是一些常用的语法元素:

1. 字符类 (Character Classes)

字符类允许你匹配一组字符中的任意一个。

语法 描述 示例 匹配项
[abc] 匹配 abc 中的任意一个字符 [abc] “cat” ca
[^abc] 匹配除了 abc 之外的任意字符 [^abc] “dog” dog
[a-zA-Z] 匹配所有大小写字母 [a-z] “Hello” ello
[0-9] 匹配所有数字(等同于 \d [0-9] “123” 123
[a-zA-Z0-9] 匹配所有字母和数字

2. 预定义字符类 (Predefined Character Classes)

Java 提供了一些方便的预定义字符类来简化常用模式。

语法 描述 示例 匹配项
. 匹配除换行符外的任何单个字符 a.b “a*b” a*b
\d 匹配一个数字字符(等同于 [0-9] \d “123” 123
\D 匹配一个非数字字符(等同于 [^0-9] \D “abc” abc
\s 匹配一个空白字符(空格、制表符、换页符等) \s “a b”
\S 匹配一个非空白字符 \S “a b” ab
\w 匹配一个单词字符(字母、数字、下划线,等同于 [a-zA-Z_0-9] \w “hello_1” hello_1
\W 匹配一个非单词字符 \W “hi!” !

3. 边界匹配器 (Boundary Matchers)

用于匹配字符串或单词的边界。

语法 描述 示例 匹配项
^ 匹配行的开头 ^cat “cat dog” cat
$ 匹配行的结尾 dog$ “cat dog” dog
\b 匹配单词边界 \bcat\b “cat” cat
\B 匹配非单词边界 \Bcat\B “tomcat” cat

4. 数量词 (Quantifiers)

数量词用于指定某个模式出现的次数。

语法 描述 示例 匹配项
? 出现零次或一次 colou?r color, colour
* 出现零次或多次 a*b b, ab, aaab
+ 出现一次或多次 a+b ab, aaab
{n} 出现恰好 n 次 a{3}b aaab
{n,} 出现至少 n 次 a{2,}b aab, aaab
{n,m} 出现 n 到 m 次 a{1,3}b ab, aab, aaab

贪婪、勉强和独占模式:

默认情况下,数量词是“贪婪的”(Greedy),它们会尽可能多地匹配字符。
贪婪 (Greedy)*, +, ?, {n,}, {n,m} 默认是贪婪的。
.* 会匹配到行尾的最后一个字符。
勉强 (Reluctant / Lazy):在数量词后加 ?,使其尽可能少地匹配字符。
*?, +?, ??, {n,}?, {n,m}?
.*? 会匹配到它能匹配的最小字符串。
独占 (Possessive):在数量词后加 +,使其尽可能多地匹配字符,但不会回溯。这可以提高性能,但有时会因不回溯而导致匹配失败。
*+, ++, ?+, {n,}+, {n,m}+

5. 逻辑运算符

语法 描述 示例 匹配项
XY X 后跟 Y ab ab
X|Y X 或 Y cat|dog cat, dog
(X) 捕获组,将 X 作为一个整体处理 (ab)+ ab, abab

6. 反向引用 (Backreferences)

通过 (X) 捕获组匹配到的内容会被存储起来,可以通过 \n(n 为组号,从 1 开始)进行引用。

  • (\d)\1:匹配连续的两个相同数字,如 “11”, “22”。
  • ([a-z])\1\1:匹配连续的三个相同小写字母,如 “aaa”, “bbb”。

7. 转义字符 (Escaping)

特殊字符(如 ., *, +, ?, |, (, ), [, ], {, }, ^, $, \) 如果要匹配它们本身,需要使用反斜杠 \ 进行转义。在 Java 字符串中,反斜杠本身也是一个特殊字符,所以需要双重转义,即 \\

  • \.:匹配字面量 .
  • \\d:匹配数字字符。

三、Java 正则表达式应用

1. PatternMatcher

Java 中使用正则表达式的核心是 PatternMatcher 类。

“`java
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexExample {
public static void main(String[] args) {
String text = “Hello, my email is [email protected] and another is [email protected].”;
String regex = “\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b”;

    // 1. 编译正则表达式
    Pattern pattern = Pattern.compile(regex);

    // 2. 创建匹配器
    Matcher matcher = pattern.matcher(text);

    // 3. 执行匹配操作
    System.out.println("查找所有邮箱地址:");
    while (matcher.find()) {
        // matcher.group() 返回与模式匹配的子序列
        System.out.println("  匹配到: " + matcher.group());
        // matcher.start() 返回匹配到的子序列的起始索引
        System.out.println("  起始索引: " + matcher.start());
        // matcher.end() 返回匹配到的子序列的结束索引加1
        System.out.println("  结束索引: " + matcher.end());
    }

    // 4. 替换操作
    System.out.println("\n替换邮箱地址:");
    String replacedText = matcher.replaceAll("[EMAIL_REDACTED]");
    System.out.println(replacedText);

    // 5. 字符串分割
    System.out.println("\n根据数字分割字符串:");
    String numbers = "one1two2three3four";
    Pattern digitPattern = Pattern.compile("\\d");
    String[] parts = digitPattern.split(numbers);
    for (String part : parts) {
        System.out.println("  分割部分: " + part);
    }

    // 6. 验证(一次性匹配)
    System.out.println("\n验证手机号码:");
    String phoneNumber = "13812345678";
    String invalidPhoneNumber = "12345";
    // ^1[3-9]\d{9}$ 匹配以1开头,第二位是3-9,后面9位数字的11位手机号码
    boolean isValid = Pattern.matches("^1[3-9]\\d{9}$", phoneNumber);
    System.out.println("'" + phoneNumber + "' 是有效的手机号码吗? " + isValid);
    boolean isInvalid = Pattern.matches("^1[3-9]\\d{9}$", invalidPhoneNumber);
    System.out.println("'" + invalidPhoneNumber + "' 是有效的手机号码吗? " + isInvalid);
}

}
“`

2. String 类中的正则表达式方法

Java 的 String 类也提供了一些便利的方法,可以直接使用正则表达式。

  • boolean matches(String regex): 判断整个字符串是否匹配给定的正则表达式。
  • String replaceAll(String regex, String replacement): 替换所有匹配正则表达式的子字符串。
  • String replaceFirst(String regex, String replacement): 替换第一个匹配正则表达式的子字符串。
  • String[] split(String regex): 根据正则表达式将字符串分割成字符串数组。

四、高级特性

1. 组 (Groups)

正则表达式中的括号 () 用于创建捕获组。每个捕获组都会有一个编号,从 1 开始。组 0 代表整个匹配的文本。

“`java
String text = “Name: John Doe, Age: 30”;
Pattern p = Pattern.compile(“Name: (\w+ \w+), Age: (\d+)”);
Matcher m = p.matcher(text);

if (m.find()) {
System.out.println(“完整匹配: ” + m.group(0)); // Name: John Doe, Age: 30
System.out.println(“姓名: ” + m.group(1)); // John Doe
System.out.println(“年龄: ” + m.group(2)); // 30
}
“`

2. 非捕获组 (Non-capturing Groups)

使用 (?:X) 可以创建非捕获组,它会匹配内容但不会存储,因此不会分配组号。这在只需要分组而不关心捕获内容时很有用,可以提高性能。

  • (?:\w+):匹配一个或多个单词字符,但不捕获。

3. 肯定/否定先行断言 (Lookahead Assertions)

  • X(?=Y) (肯定先行断言): 匹配 X,当 X 后面跟着 Y 时。Y 不会被包含在匹配结果中。
    • Windows(?=\d): 匹配后面跟着数字的 “Windows”。
  • X(?!Y) (否定先行断言): 匹配 X,当 X 后面没有跟着 Y 时。
    • Windows(?!\d): 匹配后面没有跟着数字的 “Windows”。

4. 肯定/否定后行断言 (Lookbehind Assertions)

  • (?<=Y)X (肯定后行断言): 匹配 X,当 X 前面是 Y 时。Y 不会被包含在匹配结果中。
    • (?<=\$)\d+: 匹配前面是 $ 符号的数字。
  • (?<!Y)X (否定后行断言): 匹配 X,当 X 前面不是 Y 时。
    • (?<!\$)\d+: 匹配前面不是 $ 符号的数字。

五、正则表达式的性能考虑

  • 编译 Pattern: Pattern.compile() 是一个耗时操作。如果正则表达式被多次使用,应该将其编译一次并重用 Pattern 对象。
  • 避免过度回溯: 复杂的正则表达式,特别是包含多个量词和交替操作的,可能会导致大量的回溯,从而降低性能甚至引发栈溢出。独占量词 *+, ++ 可以避免回溯。
  • 具体化模式: 尽可能使用更具体的模式来缩小匹配范围。例如,用 \d 代替 . 来匹配数字。
  • 使用非捕获组: 如果你不需要捕获组的内容,使用 (?:X) 非捕获组可以稍微提高性能。

总结

Java 正则表达式是一个功能强大且灵活的工具,能够解决各种复杂的字符串处理问题。通过理解其基础概念、核心语法和应用方式,并注意性能优化,开发者可以更高效、更优雅地处理文本数据。掌握正则表达式是提升 Java 编程能力的关键一步。

滚动至顶部