Java正则表达式详解:从入门到实战,搞定文本处理难题
在Java开发中,文本处理是高频场景——表单验证、日志提取、字符串清洗、数据解析等,都离不开正则表达式的支持。正则表达式(Regular Expression,简称Regex)本质是一套字符匹配的规则模板,用简洁的符号组合,就能快速实现“判断字符串是否符合规则”“提取符合条件的内容”“替换指定内容”等操作,极大提升开发效率。
本文将从Java正则的核心基础、核心类用法、实战场景、常见避坑点四个维度,结合具体代码示例,帮你快速掌握正则表达式,轻松应对各类文本处理需求,同时兼顾面试高频考点。
一、正则表达式核心基础:语法入门
正则表达式的核心是“符号组合”,这些符号分为「普通字符」和「元字符」,元字符是正则的精髓,也是实现灵活匹配的关键。需要特别注意:Java中所有带反斜杠\的正则符号,都要写成\\(因为Java字符串中\本身需要转义),这是初学者最易踩坑的点之一。
1.1 普通字符
普通字符即日常使用的字母、数字、汉字、普通符号(如a-z、0-9、中、@),正则中写什么,就匹配字符串中的什么,无特殊含义。
示例:正则abc,只能匹配字符串abc,不匹配ab、abcd、aBc(正则默认区分大小写)。
1.2 核心元字符(必记)
元字符是具有特殊含义的符号,能匹配某一类符合条件的字符,无需逐个枚举,是正则简化书写的核心。以下按使用频率排序,记熟这些就能解决80%的日常需求:
| 元字符 | 含义 | 示例 |
|---|---|---|
| . | 匹配任意1个字符(除了换行\n) | 正则a.b可匹配a1b、a@b、aab |
| \d | 匹配任意1个数字(0-9) | 正则\\d\\d可匹配12、99 |
| \D | 匹配任意1个非数字 | 正则\\D可匹配a、@、中 |
| \w | 匹配任意1个单词字符(字母/数字/下划线),可匹配汉字 | 正则\\w可匹配A、5、_、张 |
| \W | 匹配任意1个非单词字符 | 正则\\W可匹配@、+、空格 |
| \s | 匹配任意1个空白字符(空格、制表符\t、换行\n) | 正则a\\sb可匹配a b、a\tb |
| \S | 匹配任意1个非空白字符 | 正则\\S可匹配1、a、@ |
| \ | 转义字符,用于匹配特殊符号(如.、*、?) | 正则\\.可匹配.,\\*可匹配* |
1.3 数量元字符(限定匹配次数)
上述元字符仅能匹配1个字符,数量元字符用于指定匹配次数,写在“要限定次数的字符/元字符”后面,是实现“多字符匹配”的核心。
| 数量元字符 | 含义 | 示例 |
|---|---|---|
| * | 匹配0次或多次(可有可无,贪婪匹配,尽可能多匹配) | 正则a*b可匹配b、ab、aaab |
| + | 匹配1次或多次(至少1次) | 正则a+b可匹配ab、aaab,不匹配b |
| ? | 匹配0次或1次(最多1次,可选) | 正则a?b可匹配b、ab,不匹配aab |
| {n} | 匹配恰好n次(n为非负整数) | 正则\\d{6}可匹配6位数字验证码(如123456) |
| {n,} | 匹配至少n次(n次及以上) | 正则\\d{2,}可匹配2位及以上数字(如12、1234) |
| {n,m} | 匹配n到m次(包含n和m) | 正则\\w{4,16}可匹配4-16位用户名(字母/数字/下划线) |
1.4 边界元字符(限定匹配位置)
默认情况下,正则会从字符串中找任意位置的匹配内容,边界元字符可限定“匹配必须在字符串的开头/结尾”,避免出现“部分匹配”的问题(如验证手机号时,避免匹配到11位数字以外的内容)。
| 边界元字符 | 含义 | 示例 |
|---|---|---|
| ^ | 匹配字符串的开头 | 正则^1,仅匹配以1开头的字符串(如13800138000) |
| $ | 匹配字符串的结尾 | 正则\\d$,仅匹配以数字结尾的字符串(如abc123) |
| \b | 匹配单词边界(单词与非单词字符的分隔处) | 正则\\bjava\\b,可匹配java,不匹配javac、ajava |
1.5 分组与捕获(进阶)
使用括号()可将正则表达式分为多个分组,用于提取结构化信息(如从日期字符串中提取年、月、日)。分组编号从1开始,group(0)表示整个匹配内容,group(1)表示第一个分组,以此类推。
示例:正则(\\d{4})-(\\d{2})-(\\d{2}),可匹配日期格式2026-04-11,其中:
-
group(0):整个匹配内容(
2026-04-11) -
group(1):第一个分组(年,
2026) -
group(2):第二个分组(月,
04) -
group(3):第三个分组(日,
11)
二、Java正则核心类:Pattern与Matcher
Java中没有内置的正则语法,而是通过java.util.regex包提供的两个核心类实现正则操作——Pattern(正则表达式的编译表示)和Matcher(执行匹配操作的引擎),二者配合使用,是Java正则的标准用法。
2.1 Pattern类:编译正则表达式
Pattern类表示一个已编译的正则表达式,它不能通过构造函数实例化,只能通过静态方法compile(String regex)创建实例。编译过程会校验正则语法的合法性,若语法错误,会抛出PatternSyntaxException。
核心特点:Pattern实例是不可变的,线程安全,可被多个线程共享;编译正则表达式有一定开销,因此频繁使用的正则,建议复用Pattern实例,避免重复编译。
常用方法:
-
Pattern compile(String regex):将字符串形式的正则表达式编译为Pattern对象。
-
Matcher matcher(CharSequence input):创建Matcher实例,用于对输入字符串执行匹配操作。
-
boolean matches(String regex, CharSequence input):静态便捷方法,一次性匹配(内部会编译正则,频繁使用效率低)。
-
String pattern():返回原始的正则表达式字符串。
2.2 Matcher类:执行匹配操作
Matcher类是状态ful的匹配引擎,通过Pattern的matcher(CharSequence input)方法创建,专门用于对输入字符串执行匹配、查找、提取、替换等操作。Matcher实例不是线程安全的,每次使用应单独创建。
常用方法(高频):
-
boolean matches():判断整个输入字符串是否完全匹配正则表达式(等价于用
^...和...$包裹正则)。 -
boolean find():从输入字符串中查找下一个匹配的子串,成功返回true,可多次调用(自动从上次结束位置继续查找)。
-
String group():返回当前匹配到的子串(需在find()或matches()返回true后调用,默认返回group(0))。
-
String group(int index):返回指定分组的内容(index从1开始)。
-
int start():返回当前匹配子串的起始位置(下标从0开始)。
-
int end():返回当前匹配子串的结束位置(不包含结束下标)。
-
String replaceAll(String replacement):将所有匹配到的子串替换为指定字符串,支持用
$1、$2引用分组。
2.3 标准使用流程(推荐)
复用Pattern实例,配合Matcher执行匹配操作,适合频繁使用的场景,效率更高,是工程实践中的推荐写法:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo {
// 复用Pattern实例,避免重复编译(推荐声明为静态常量)
private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
public static void main(String[] args) {
String input = "今天是2026-04-11,明天是2026-04-12";
// 1. 创建Matcher实例
Matcher matcher = DATE_PATTERN.matcher(input);
// 2. 查找所有匹配的子串(日期)
while (matcher.find()) {
String fullDate = matcher.group(0); // 整个匹配内容
String year = matcher.group(1); // 年(第一个分组)
String month = matcher.group(2); // 月(第二个分组)
String day = matcher.group(3); // 日(第三个分组)
System.out.printf("完整日期:%s,年:%s,月:%s,日:%s%n", fullDate, year, month, day);
}
}
}
输出结果:
2.4 便捷写法(适合一次性匹配)
若仅需执行一次匹配操作,可使用Pattern的静态方法matches(),或String类的matches()方法(底层也是调用Pattern),写法更简洁,但效率较低(每次都会重新编译正则)。
// 方式1:Pattern静态方法
boolean isNumber = Pattern.matches("\\d+", "12345"); // true
// 方式2:String类方法(底层调用Pattern.matches())
boolean isPhone = "13800138000".matches("^1[3-9]\\d{9}$"); // true
三、Java正则实战场景(开发高频)
结合实际开发场景,整理以下高频正则用法,直接复制可用,同时覆盖表单验证、日志提取、字符串清洗等核心需求。
3.1 表单验证(最常用)
表单验证是正则的核心应用场景,如手机号、邮箱、身份证、用户名等格式校验,以下是常用正则及实现代码:
import java.util.regex.Pattern;
/**
* 表单验证工具类(常用正则封装)
*/
public class RegexValidator {
// 手机号(中国,三大运营商,11位)
public static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
// 邮箱(常见格式,支持字母、数字、下划线、点号、连字符)
public static final Pattern EMAIL_PATTERN = Pattern.compile("^(a-zA-Z0-9_.+-)+@(a-zA-Z0-9-)+\\.(a-zA-Z0-9-.)+$");
// 身份证(18位,最后一位可为X/x)
public static final Pattern ID_CARD_PATTERN = Pattern.compile("^\\d{17}(\\d|X|x)$");
// 用户名(4-16位,字母、数字、下划线、连字符)
public static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]{4,16}$");
// 密码(6-20位,包含字母和数字,可选特殊符号)
public static final Pattern PASSWORD_PATTERN = Pattern.compile("^(?=.*[a-zA-Z])(?=.*\\d).{6,20}$");
/**
* 验证字符串是否符合指定正则
* @param pattern 正则Pattern
* @param input 待验证字符串
* @return 符合返回true,否则false
*/
public static boolean validate(Pattern pattern, String input) {
if (input == null || input.isEmpty()) {
return false;
}
return pattern.matcher(input).matches();
}
// 测试
public static void main(String[] args) {
System.out.println(validate(PHONE_PATTERN, "13800138000")); // true
System.out.println(validate(EMAIL_PATTERN, "test@163.com")); // true
System.out.println(validate(ID_CARD_PATTERN, "110101199001011234")); // true
System.out.println(validate(USERNAME_PATTERN, "user_123")); // true
System.out.println(validate(PASSWORD_PATTERN, "abc123")); // true
}
}
3.2 日志提取(实战重点)
开发中经常需要从日志中提取关键信息(如时间戳、日志级别、请求ID),正则是最高效的方式。示例:从日志中提取时间戳和日志级别。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LogExtractDemo {
public static void main(String[] args) {
// 日志内容
String log = "2026-04-11 10:30:00 INFO [main] com.example.Demo - 程序启动成功\n" +
"2026-04-11 10:30:05 ERROR [main] com.example.Demo - 数据库连接失败\n" +
"2026-04-11 10:30:10 WARN [main] com.example.Demo - 配置文件未找到";
// 正则:匹配时间戳(yyyy-MM-dd HH:mm:ss)和日志级别(INFO/ERROR/WARN)
Pattern logPattern = Pattern.compile("(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) (INFO|ERROR|WARN)");
Matcher matcher = logPattern.matcher(log);
// 提取所有匹配的信息
while (matcher.find()) {
String time = matcher.group(1);
String level = matcher.group(2);
System.out.printf("时间:%s,日志级别:%s%n", time, level);
}
}
}
输出结果:
3.3 字符串清洗与替换
正则可快速实现字符串清洗(如去除空白字符、特殊字符、HTML标签)和指定内容替换(如敏感词替换)。
import java.util.regex.Pattern;
public class StringCleanDemo {
public static void main(String[] args) {
String input = " 这是一段带有 空白字符、特殊字符!@# 和HTML标签<div>的文本 \n";
// 1. 去除所有空白字符(空格、制表符、换行)
String noBlank = input.replaceAll("\\s+", "");
System.out.println("去除空白后:" + noBlank); // 这是一段带有空白字符、特殊字符!@#和HTML标签<div>的文本
// 2. 去除特殊字符(保留中英文、数字)
String noSpecial = noBlank.replaceAll("[^a-zA-Z0-9\u4e00-\u9fa5]", "");
System.out.println("去除特殊字符后:" + noSpecial); // 这是一段带有空白字符特殊字符和HTML标签div的文本
// 3. 替换敏感词(将"空白字符"替换为"***")
String noSensitive = noSpecial.replaceAll("空白字符", "***");
System.out.println("替换敏感词后:" + noSensitive); // 这是一段带有***特殊字符和HTML标签div的文本
}
}
3.4 字符串分割
使用正则分割字符串,可解决“多分隔符分割”“不定长分隔符分割”的问题(String的split()方法支持正则)。
public class StringSplitDemo {
public static void main(String[] args) {
// 多分隔符分割(逗号、分号、空格)
String str1 = "java,python;c++ javaScript";
String[] arr1 = str1.split("[,;\\s]+");
for (String s : arr1) {
System.out.print(s + " "); // java python c++ javaScript
}
System.out.println();
// 不定长分隔符分割(多个连字符)
String str2 = "java---python--c++-javascript";
String[] arr2 = str2.split("-+");
for (String s : arr2) {
System.out.print(s + " "); // java python c++ javascript
}
}
}
四、Java正则避坑指南(面试+开发重点)
正则看似简单,但容易出现语法错误、匹配异常、性能问题,以下是高频坑点及解决方案,也是面试中常考的知识点。
4.1 转义字符坑(最易踩坑)
Java中,正则字符串中的\需要转义为\\,否则会被Java字符串解析为转义字符,导致正则失效。
补充:若需匹配\本身,正则需写为\\\\(Java字符串解析后为\\,正则引擎解析后为\)。
4.2 贪婪匹配与非贪婪匹配坑
默认情况下,数量元字符(*、+、?)是贪婪匹配,会尽可能多匹配内容,可能导致匹配结果不符合预期。在数量元字符后加?,可改为非贪婪匹配(尽可能少匹配)。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GreedyDemo {
public static void main(String[] args) {
String input = "<div>Java</div><div>Python</div>";
// 贪婪匹配(默认):.*会尽可能多匹配,导致匹配整个字符串
Pattern greedyPattern = Pattern.compile("<div>.*</div>");
Matcher greedyMatcher = greedyPattern.matcher(input);
while (greedyMatcher.find()) {
System.out.println("贪婪匹配:" + greedyMatcher.group()); // <div>Java</div><div>Python</div>
}
// 非贪婪匹配:.*?尽可能少匹配,匹配到第一个</div>就停止
Pattern nonGreedyPattern = Pattern.compile("<div>.*?</div>");
Matcher nonGreedyMatcher = nonGreedyPattern.matcher(input);
while (nonGreedyMatcher.find()) {
System.out.println("非贪婪匹配:" + nonGreedyMatcher.group()); // 分别匹配两个div标签
}
}
}
4.3 性能陷阱:灾难性回溯
Java的正则引擎是回溯引擎,当正则表达式存在嵌套量词(如(a+)+)或模糊匹配时,遇到不匹配的字符串,会进行大量回溯,导致CPU占用飙升、程序卡顿,甚至崩溃。
避坑方案:
-
避免使用嵌套量词(如
(a+)+、(.*)*); -
使用占有量词(
++、*+、?+)替代贪婪量词,避免回溯; -
复杂正则尽量拆分,降低匹配复杂度。
4.4 边界匹配坑
验证格式时,忘记使用^和$,会导致“部分匹配”,比如验证手机号时,匹配到包含11位数字的字符串就返回true,而非严格的11位数字。
4.5 线程安全坑
Pattern实例是线程安全的,可被多个线程共享;但Matcher实例不是线程安全的,不能在多个线程中共享同一个Matcher对象,否则会导致匹配结果异常。
正确做法:每个线程单独创建Matcher实例,或使用局部变量存储Matcher。
五、总结
开发中,建议将常用正则封装为工具类,复用Pattern实例,避免重复编译;同时注意避坑,尤其是转义字符、贪婪匹配、性能回溯等问题。面试中,正则的语法、核心类用法、避坑点是高频考点,结合本文的实战示例,可轻松应对。 Java正则表达式详解:从入门到实战,搞定文本处理难题 在Java开发中,文本处理是高频场景——表单验证、日志提取、字符串清洗、数据解析等,都离不开正则表达