Java正则表达式核心教程与实战示例 – wiki大全

Java 正则表达式核心教程与实战

Java Regex

摘要

正则表达式是处理字符串的强大工具,广泛应用于文本搜索、格式验证、内容替换等多种场景。Java 通过 java.util.regex 包提供了完整的正则表达式支持。本教程将从基础概念讲起,深入探讨 Java 中正则表达式的核心 API、语法规则,并通过丰富的实战示例,帮助你完全掌握在 Java 项目中应用正则表达式的技巧。


目录

  1. 什么是正则表达式?
  2. Java 正则表达式核心 API
  3. 正则表达式核心语法
  4. Matcher 类的常用方法
  5. 实战示例
  6. 性能与最佳实践
  7. 总结

1. 什么是正则表达式?

正则表达式(Regular Expression, Regex)是一种特殊的字符串序列,它定义了一个“搜索模式”。通过这个模式,你可以在文本中:

  • 验证:检查一个字符串是否符合特定格式(如邮箱、手机号)。
  • 搜索:在一个大文本中查找所有符合模式的子字符串。
  • 替换:找到匹配的子字符串并将其替换为其他内容。
  • 提取:从字符串中抽取出需要的部分(如URL中的域名)。

它的强大之处在于其表达能力,能够用简洁的模式描述复杂的文本规则。

2. Java 正则表达式核心 API

Java 的正则表达式功能主要由 java.util.regex 包中的三个核心类提供。

Pattern

Pattern 对象是正则表达式的编译后表示。一个正则表达式字符串首先需要被编译成一个 Pattern 实例,这个过程会进行语法检查和性能优化。

  • 创建: Pattern 类没有公共构造函数。我们必须使用其静态方法 compile() 来创建实例。
    java
    Pattern pattern = Pattern.compile("a*b");
  • 重要性: Pattern 对象是线程安全的,并且可以被重复使用。对于需要多次使用的正则表达式,强烈建议将其编译一次并缓存起来,而不是每次使用都重新编译,这样可以显著提升性能。

Matcher

Matcher 对象是解释 Pattern 并对输入字符串执行匹配操作的引擎。

  • 创建: Matcher 实例通过 Pattern 对象的 matcher() 方法创建。
    java
    Matcher matcher = pattern.matcher("aaaaab");
  • 重要性: Matcher 对象不是线程安全的,每个线程都应该有自己的 Matcher 实例。它存储了针对特定字符串的匹配状态(如上次匹配的位置)。

PatternSyntaxException

这是一个非受检异常(Unchecked Exception),当 Pattern.compile() 方法接收到一个语法不正确的正则表达式字符串时,会抛出此异常。

java
try {
Pattern.compile("[a-z"); // 缺少闭合的 ']'
} catch (PatternSyntaxException e) {
System.err.println("正则表达式语法错误: " + e.getMessage());
}

3. 正则表达式核心语法

掌握正则表达式的关键在于理解其语法和元字符。

元字符

元字符是在正则表达式中具有特殊含义的字符。

元字符 描述 示例
. 匹配除换行符 \n 之外的任何单个字符。 a.c 匹配 “abc”, “a_c”
\d 匹配一个数字,等价于 [0-9] \d{3} 匹配 “123”
\D 匹配一个非数字字符。 \D 匹配 “a”, “#”
\s 匹配任何空白字符(空格, \t, \n)。 a\sb 匹配 “a b”
\S 匹配任何非空白字符。 \S+ 匹配 “hello”
\w 匹配字母、数字或下划线,等价于 [a-zA-Z0-9_] \w+ 匹配 “word_123”
\W 匹配非字母、数字或下划线字符。 \W 匹配 “@”, ” “

字符类

使用方括号 [] 定义一组字符。

字符类 描述 示例
[abc] 匹配 “a”, “b”, 或 “c” 中的任意一个。 [aeiou] 匹配任何元音字母
[^abc] 否定,匹配除 “a”, “b”, “c” 之外的任何字符。 [^0-9] 匹配任何非数字字符
[a-zA-Z] 范围,匹配从 “a”到”z”或”A”到”Z”的任何一个字符。 [a-z]{5} 匹配5个小写字母

边界匹配

边界匹配符用于定位在字符串中特定位置的匹配。

边界符 描述 示例
^ 匹配行的开头。 ^Hello 匹配以 “Hello” 开头的字符串
$ 匹配行的结尾。 world$ 匹配以 “world” 结尾的字符串
\b 单词边界。匹配单词字符和非单词字符之间的位置。 \bcat\b 匹配独立的 “cat”
\B 非单词边界。 \Bcat\B 匹配 “Tomcat” 中的 “cat”

量词(Quantifiers)

量词用于指定一个模式需要匹配的次数。

量词 描述 示例
* 匹配前一个元素零次或多次。 go*gle 匹配 “ggle”, “google”
+ 匹配前一个元素一次或多次。 go+gle 匹配 “gogle”, “google”
? 匹配前一个元素零次或一次。 colou?r 匹配 “color”, “colour”
{n} 匹配前一个元素恰好 n 次。 \d{4} 匹配 “2024”
{n,} 匹配前一个元素至少 n 次。 \d{2,} 匹配 “12”, “1234”
{n,m} 匹配前一个元素从 n 次到 m 次。 \d{2,4} 匹配 “12”, “123”, “1234”

贪婪、懒惰与独占模式

  • 贪婪模式 (Greedy): 默认模式。量词会尽可能多地匹配字符。例如,a.*b 对于 “axbyazb” 会匹配整个字符串。
  • 懒惰模式 (Reluctant/Lazy): 在量词后添加 ?。会尽可能少地匹配字符。例如,a.*?b 对于 “axbyazb” 会先匹配 “axb”,再次调用 find() 会匹配 “azb”。
  • 独占模式 (Possessive): 在量词后添加 +。会尽可能多地匹配,但不会回溯。性能稍好,但使用场景较少。

分组与捕获

使用圆括号 () 可以将多个字符作为一个整体对待,并且会“捕获”这个分组匹配到的内容,以便后续引用。

  • 分组: (dog)+ 会匹配 “dog”, “dogdog” 等。
  • 捕获: (\d{4})-(\d{2}) 在匹配 “2024-12” 时,会捕获两个分组:
    • group(1) 的内容是 “2024”。
    • group(2) 的内容是 “12”。
    • group(0) 始终是整个匹配的字符串 “2024-12″。

特殊构造(非捕获)

构造 描述
(?:...) 非捕获分组:只分组,不捕获,不计入 groupCount
(?=...) 正向先行断言:匹配位置右侧必须满足表达式。
(?!...) 负向先行断言:匹配位置右侧必须不满足表达式。
(?<=...) 正向后行断言:匹配位置左侧必须满足表达式。
(?<!...) 负向后行断言:匹配位置左侧必须不满足表达式。

4. Matcher 类的常用方法

假设我们有 Pattern pattern = Pattern.compile(regex);Matcher matcher = pattern.matcher(input);

  • boolean matches(): 尝试将整个输入字符串与模式进行匹配。如果整个字符串完全匹配,则返回 true
  • boolean find(): 扫描输入字符串,查找与模式匹配的下一个子序列。每次调用都会从上一次匹配结束的位置继续查找。
  • boolean lookingAt(): 尝试将输入字符串从开头开始与模式进行匹配。
  • String group(): 返回由上一次匹配操作(如 find())所捕获的子序列。等价于 group(0)
  • String group(int group): 返回在匹配期间由给定分组捕获的子序列。
  • int groupCount(): 返回此匹配器模式中的捕获分组数量。
  • String replaceAll(String replacement): 替换输入字符串中所有匹配模式的子序列。
  • String replaceFirst(String replacement): 替换输入字符串中第一个匹配模式的子序列。

5. 实战示例

示例 1:验证邮箱地址

一个相对简单但常用的邮箱正则表达式。

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

public class EmailValidator {
// 邮箱正则:用户名@域名
// 用户名: 字母、数字、下划线、点、减号
// 域名: 字母、数字、减号,后跟点,最后是2-6个字母的顶级域名
private static final String EMAIL_REGEX =
“^[\w\.-]+@([\w-]+\.)+[a-zA-Z]{2,6}$”;

private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);

public static boolean isValidEmail(String email) {
    if (email == null) {
        return false;
    }
    Matcher matcher = EMAIL_PATTERN.matcher(email);
    return matcher.matches();
}

public static void main(String[] args) {
    System.out.println("[email protected] is valid: " + isValidEmail("[email protected]")); // true
    System.out.println("[email protected] is valid: " + isValidEmail("[email protected]")); // true
    System.out.println("[email protected] is valid: " + isValidEmail("[email protected]")); // false
    System.out.println("test@com is valid: " + isValidEmail("test@com")); // false
}

}
“`

示例 2:提取字符串中的电话号码

从一段文本中找出所有可能是电话号码的数字串(假设为11位数字)。

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

public class PhoneNumberExtractor {
// 匹配11位数字的电话号码
private static final Pattern PHONE_PATTERN = Pattern.compile(“\b1\d{10}\b”);

public static List<String> findPhoneNumbers(String text) {
    List<String> numbers = new ArrayList<>();
    Matcher matcher = PHONE_PATTERN.matcher(text);

    // 使用 find() 方法循环查找所有匹配项
    while (matcher.find()) {
        numbers.add(matcher.group());
    }
    return numbers;
}

public static void main(String[] args) {
    String text = "联系人: 张三 13812345678, 李四 15987654321。无效号码: 12345。";
    List<String> foundNumbers = findPhoneNumbers(text);
    System.out.println("找到的电话号码: " + foundNumbers); // [13812345678, 15987654321]
}

}
“`

示例 3:替换敏感词

将文本中的敏感词替换为星号。

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

public class SensitiveWordFilter {
public static void main(String[] args) {
String text = “这是一段包含不良言论和违禁词的文本。”;
String filteredText = text.replaceAll(“不良言论|违禁词”, “***”);
System.out.println(“原文: ” + text);
System.out.println(“过滤后: ” + filteredText);
}
}
``
**注意**:
String类自带的replaceAll` 方法内部就是基于正则表达式的,对于简单的一次性替换,可以直接使用。

示例 4:从 HTML 中提取链接

从一个简单的 HTML 字符串中提取所有 <a> 标签的 href 属性值。

警告: 正则表达式不适合解析复杂的、结构不规则的 HTML/XML。对于正式的应用,请使用专用的 HTML 解析库(如 Jsoup)。这里仅作演示。

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

public class LinkExtractor {
// 使用懒惰量詞 .? 来避免贪婪匹配
private static final Pattern LINK_PATTERN = Pattern.compile(“<a\s+href=\”(.
?)\””);

public static void main(String[] args) {
    String html = "<ul>" +
                  "<li><a href=\"https://example.com/page1\">Page 1</a></li>" +
                  "<li><a href=\"/local/page2.html\">Page 2</a></li>" +
                  "<li><a href=\"https://google.com\">Google</a></li>" +
                  "</ul>";

    Matcher matcher = LINK_PATTERN.matcher(html);
    while (matcher.find()) {
        // group(1) 捕获的是 href="..." 中括号内的内容
        System.out.println("找到链接: " + matcher.group(1));
    }
}

}
“`

示例 5:使用 split 方法分割字符串

Pattern.split() 方法允许你使用复杂的模式来分割字符串。

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

public class SplitExample {
public static void main(String[] args) {
// 使用一个或多个逗号或分号作为分隔符
Pattern pattern = Pattern.compile(“[,;]+”);
String text = “apple,banana;;cherry,date”;
String[] fruits = pattern.split(text);
System.out.println(Arrays.toString(fruits)); // [apple, banana, cherry, date]
}
}
“`

6. 性能与最佳实践

  1. 缓存 Pattern 对象: 如前所述,如果一个正则表达式需要被多次使用,请将其编译为一个静态的 Pattern常量。Pattern.compile() 是一个昂贵的操作。
  2. 使用 matches() 进行全匹配验证: 当你需要验证整个字符串是否符合格式时(如邮箱、密码),matches() 是最直接且意图最清晰的方法。
  3. 注意贪婪量词: 贪婪量词(*, +)可能会导致性能问题,尤其是在处理大文本时,可能引发“灾難性回溯”。在可能的情况下,优先使用懒惰量词(*?, +?)或更精确的模式。
  4. 使用非捕获分组: 如果你只需要分组来应用量词,但不需要引用分组的内容,请使用非捕获分组 (?:...)。这样可以减少内存开销,并略微提升性能。
  5. 为简单场景选择 String 方法: 对于简单的、一次性的匹配或替换,直接使用 String.matches(), String.replaceAll(), String.split() 更方便。

7. 总结

Java 的正则表达式功能强大且高效。掌握 PatternMatcher 类的使用方法,并熟悉正则表达式的核心语法,将极大地提升你处理文本数据的能力。从简单的格式验证到复杂的数据提取,正则表达式都是你工具箱中不可或缺的一员。

多写多练是掌握正则表达式的唯一途径。希望这篇教程能为你打下坚实的基础。

滚动至顶部