Java 正则表达式详解

正则表达式(Regular Expression)是一种强大的文本处理工具,它通过定义特定模式来匹配、查找和替换文本。Java 自 JDK 1.4 起内置了对正则表达式的支持,主要通过 java.util.regex 包中的 PatternMatcher 和 PatternSyntaxException 类实现。本文将系统讲解 Java 正则表达式的核心概念、语法规则及实战技巧。

一、Java 正则表达式基础

1. 核心类与工作流程

Java 正则表达式的处理主要涉及两个核心类:

  • Pattern 类:编译正则表达式后创建的模式对象,线程安全,可复用
  • Matcher 类:对输入字符串进行匹配操作的引擎,线程不安全

基本工作流程
 
import java.util.regex.*;

public class RegexExample {
    public static void main(String[] args) {
        // 1. 编译正则表达式,生成Pattern对象
        Pattern pattern = Pattern.compile("a*b");
        
        // 2. 创建Matcher对象,关联待匹配的字符串
        Matcher matcher = pattern.matcher("aaaaab");
        
        // 3. 执行匹配操作
        boolean matchFound = matcher.matches();
        System.out.println(matchFound); // 输出: true
    }
}
 

2. 正则表达式语法基础

Java 正则表达式支持的基本元字符和语法包括:

(1)字符类

  • [abc]:匹配 a、b 或 c 中的任意一个字符
  • [^abc]:匹配除 a、b、c 之外的任意字符
  • [a-zA-Z]:匹配任意大小写字母
  • [0-9]:匹配任意数字

(2)预定义字符类

  • .:匹配任意单个字符(除换行符)
  • \d:等价于 [0-9],匹配数字
  • \D:等价于 [^0-9],匹配非数字
  • \s:匹配空白字符(空格、制表符、换行符等)
  • \S:匹配非空白字符
  • \w:等价于 [a-zA-Z0-9_],匹配字母、数字和下划线
  • \W:等价于 [^a-zA-Z0-9_],匹配非字母、数字和下划线

(3)数量词

  • X?:X 出现 0 次或 1 次(可选)
  • X*:X 出现 0 次或多次
  • X+:X 出现 1 次或多次
  • X{n}:X 恰好出现 n 次
  • X{n,}:X 至少出现 n 次
  • X{n,m}:X 出现次数介于 n 和 m 之间(含)

(4)边界匹配

  • ^:匹配输入的开始位置
  • $:匹配输入的结束位置
  • \b:匹配单词边界(如空格、标点或字符串首尾)

二、Matcher 类的核心方法

Matcher 类提供了三个主要方法用于执行匹配操作:

1. matches()

  • 功能:尝试将整个输入字符串与正则表达式匹配
  • 返回值:完全匹配返回 true,否则返回 false
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("12345");
System.out.println(matcher.matches()); // true

matcher = pattern.matcher("123abc");
System.out.println(matcher.matches()); // false
 

2. find()

  • 功能:在输入字符串中查找下一个匹配的子序列
  • 返回值:找到匹配返回 true,否则返回 false
  • 特点:可多次调用,用于查找所有匹配项
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("abc123def456");

while (matcher.find()) {
    System.out.println("匹配到: " + matcher.group());
}
// 输出:
// 匹配到: 123
// 匹配到: 456
 

3. lookingAt()

  • 功能:从输入字符串的开始位置尝试匹配正则表达式
  • 返回值:如果字符串的前缀匹配模式,返回 true,否则 false
  • 与 matches() 的区别:不要求整个字符串匹配,只检查前缀
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("123abc");
System.out.println(matcher.lookingAt()); // true(前缀"123"匹配)
System.out.println(matcher.matches());   // false(整个字符串不匹配)
 

三、分组与捕获

1. 基本分组

  • 语法:使用圆括号 () 定义分组
  • 编号规则:从 1 开始,按左括号出现的顺序编号
  • 特殊组:组 0 表示整个匹配的文本
 
Pattern pattern = Pattern.compile("(\\d{3})-(\\d{4})");
Matcher matcher = pattern.matcher("电话: 010-1234");

if (matcher.find()) {
    System.out.println("完整匹配: " + matcher.group(0)); // 010-1234
    System.out.println("第一组: " + matcher.group(1));   // 010
    System.out.println("第二组: " + matcher.group(2));   // 1234
}
 

2. 非捕获分组

  • 语法(?:pattern)
  • 作用:仅用于分组,但不捕获匹配的文本,不分配组号
  • 优势:提高性能,减少内存占用
 
// 非捕获分组示例:匹配IP地址
Pattern pattern = Pattern.compile("(?:\\d{1,3}\\.){3}\\d{1,3}");
Matcher matcher = pattern.matcher("IP: 192.168.1.1");

if (matcher.find()) {
    System.out.println("匹配到IP: " + matcher.group()); // 192.168.1.1
}
 

3. 反向引用

  • 语法\n(n 为组号)
  • 作用:在正则表达式内部引用前面捕获的分组内容
// 匹配重复单词(如"hello hello")
Pattern pattern = Pattern.compile("(\\w+) \\1");
Matcher matcher = pattern.matcher("hello hello world");

if (matcher.find()) {
    System.out.println("重复单词: " + matcher.group()); // hello hello
}
 

四、高级特性

1. 贪婪与非贪婪匹配

  • 贪婪匹配:数量词(如 *+)默认尽可能多地匹配
  • 非贪婪匹配:在数量词后加 ?,改为尽可能少地匹配

String text = "<html><body><h1>标题</h1></body></html>";

// 贪婪匹配(默认)
Pattern greedyPattern = Pattern.compile("<.*>");
Matcher greedyMatcher = greedyPattern.matcher(text);
if (greedyMatcher.find()) {
    System.out.println("贪婪匹配: " + greedyMatcher.group()); 
    // 输出: <html><body><h1>标题</h1></body></html>
}

// 非贪婪匹配
Pattern nonGreedyPattern = Pattern.compile("<.*?>");
Matcher nonGreedyMatcher = nonGreedyPattern.matcher(text);
while (nonGreedyMatcher.find()) {
    System.out.println("非贪婪匹配: " + nonGreedyMatcher.group()); 
    // 输出: <html>、<body>、<h1>、</h1>、</body>、</html>
}
 

2. 零宽断言

零宽断言用于在特定位置进行匹配检查,但不消耗字符。

(1)正向预查(Positive Lookahead)

  • 语法(?=pattern)
  • 作用:匹配位置后面必须跟着 pattern
 
// 匹配密码(至少8位,包含数字)
Pattern pattern = Pattern.compile("^(?=.*\\d).{8,}$");
Matcher matcher = pattern.matcher("abc12345");
System.out.println(matcher.matches()); // true
 

(2)负向预查(Negative Lookahead)

  • 语法(?!pattern)
  • 作用:匹配位置后面不能跟着 pattern
// 匹配不包含"cat"的字符串
Pattern pattern = Pattern.compile("^(?!.*cat).*$");
Matcher matcher = pattern.matcher("dog");
System.out.println(matcher.matches()); // true
 

(3)正向后行(Positive Lookbehind)

  • 语法(?<=pattern)
  • 作用:匹配位置前面必须是 pattern
// 匹配美元价格(如$100)
Pattern pattern = Pattern.compile("(?<=\\$)\\d+");
Matcher matcher = pattern.matcher("价格:$100");
if (matcher.find()) {
    System.out.println("金额: " + matcher.group()); // 100
}
 

3. 标志位(Pattern Flags)

通过 Pattern.compile() 的第二个参数设置标志位,影响匹配行为。

 
// 使用CASE_INSENSITIVE忽略大小写
Pattern pattern = Pattern.compile("java", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher("Java Programming");
System.out.println(matcher.find()); // true

// 多行模式(^和$匹配每行的开始和结束)
Pattern multiLinePattern = Pattern.compile("^Hello", Pattern.MULTILINE);
Matcher multiLineMatcher = multiLinePattern.matcher("Hi\nHello World");
System.out.println(multiLineMatcher.find()); // true
 

五、字符串替换与分割

1. 替换方法

  • replaceAll(String replacement):替换所有匹配项
  • replaceFirst(String replacement):替换第一个匹配项
  • appendReplacement(StringBuffer sb, String replacement):自定义替换逻辑

String text = "Hello 123 World 456";
// 替换所有数字为X
String result = text.replaceAll("\\d", "X");
System.out.println(result); // Hello XXX World XXX

// 使用appendReplacement实现复杂替换
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher(text);
StringBuffer sb = new StringBuffer();

while (matcher.find()) {
    int num = Integer.parseInt(matcher.group()) * 2;
    matcher.appendReplacement(sb, String.valueOf(num));
}
matcher.appendTail(sb);
System.out.println(sb.toString()); // Hello 246 World 912
 

2. 字符串分割

String.split() 方法底层使用正则表达式实现分割:
String text = "a,b;c|d";
// 使用多种分隔符分割
String[] parts = text.split("[,;|]");
for (String part : parts) {
    System.out.println(part); // 输出: a, b, c, d
}
 

六、性能优化与最佳实践

1. 性能优化建议

  • 预编译 Pattern:避免在循环中重复编译正则表达式
  • 使用非捕获分组:减少不必要的捕获,提高效率
  • 优先使用 startsWith()/endsWith():简单匹配场景比正则更高效
  • 避免复杂正则:过度复杂的正则表达式会显著降低性能

2. 常见陷阱

  • 量词滥用:如 .* 可能导致回溯爆炸
  • 忽略边界:忘记使用 ^ 和 $ 可能导致意外匹配
  • 错误转义:Java 字符串中 \ 需要转义为 \\

3. 工具推荐

  • RegexBuddy:强大的正则表达式调试工具
  • IDE 辅助:IntelliJ IDEA 等 IDE 提供正则表达式可视化功能

总结

Java 正则表达式通过 Pattern 和 Matcher 类提供了灵活且强大的文本处理能力。掌握基本语法(字符类、数量词、分组)、核心方法(matches()find())以及高级特性(零宽断言、标志位),能够应对各种复杂的文本匹配、查找和替换需求。在实际应用中,需注意性能优化和常见陷阱,合理使用正则表达式,避免过度复杂的模式定义。通过不断练习和实践,正则表达式将成为你处理文本数据的得力工具

posted on 2025-07-10 13:51  coding博客  阅读(676)  评论(0)    收藏  举报