mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
849 字
2 分钟
Java正则表达式详解
2026-02-01

正则表达式学的时候觉得像天书,各种符号看得眼花缭乱。但用多了才发现它是一把锋利的瑞士军刀——校验手机号、提取日志关键信息,一行正则能顶几十行if-else。这篇文章从最基础的语法开始,带你一步步掌握正则。

Java正则表达式详解#

正则表达式语法速查#

字符类#

表达式含义
[abc]匹配a、b、c中的任意一个
[^abc]匹配除了a、b、c之外的任意字符
[a-z]匹配a到z的任意小写字母
[0-9]匹配任意数字

预定义字符类#

表达式含义
.匹配任意字符(除了换行符)
\d数字,等价于[0-9]
\D非数字
\w单词字符(字母、数字、下划线)
\s空白字符(空格、制表符、换行)

边界匹配#

表达式含义
^字符串开头
$字符串结尾
\b单词边界

量词#

表达式含义
*0次或多次
+1次或多次
?0次或1次
{n}恰好n次
{n,}至少n次
{n,m}n到m次

贪婪 vs 非贪婪#

默认是贪婪匹配(尽可能多匹配),加?变成非贪婪(尽可能少匹配):

String text = "<div>内容1</div><div>内容2</div>";
// 贪婪:匹配整个字符串
text.replaceAll("<div>.*</div>", "");
// 结果:""(全部被替换掉了)
// 非贪婪:只匹配第一个div
text.replaceAll("<div>.*?</div>", "");
// 结果:<div>内容2</div>

分组#

表达式含义
(xyz)捕获组
(?:xyz)非捕获组(不保存匹配结果,性能更好)
(?<name>xyz)命名捕获组

Java中使用正则#

Pattern 和 Matcher#

String regex = "\\d{3}-\\d{3}-\\d{4}"; // 匹配 xxx-xxx-xxxx
String text = "我的电话是123-456-7890,他的是987-654-3210。";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println("找到: " + matcher.group()); // 匹配到的内容
System.out.println("位置: " + matcher.start() + "-" + matcher.end());
}

String类里的正则方法#

// matches():完全匹配
"user@example.com".matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");
// split():分割
"apple,banana,orange".split(","); // → ["apple", "banana", "orange"]
// replaceAll():全部替换
"Hello 123 World 456".replaceAll("\\d+", "*"); // → "Hello * World *"
// replaceFirst():替换第一个
"Hello 123 World 456".replaceFirst("\\d+", "*"); // → "Hello * World 456"

实战常用正则#

1. 邮箱校验#

public static boolean isValidEmail(String email) {
String regex = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}";
return email.matches(regex);
}

2. 手机号校验(简单版)#

public static boolean isValidPhone(String phone) {
return phone.matches("\\d{3}-\\d{3}-\\d{4}");
}

3. 提取数字#

public static List<String> extractNumbers(String text) {
List<String> numbers = new ArrayList<>();
Matcher matcher = Pattern.compile("\\d+").matcher(text);
while (matcher.find()) {
numbers.add(matcher.group());
}
return numbers;
}

4. 驼峰转下划线#

public static String camelToSnake(String camelCase) {
return camelCase.replaceAll("([a-z0-9])([A-Z])", "$1_$2").toLowerCase();
}
// camelToSnake("userName") → "user_name"

5. 下划线转驼峰#

public static String snakeToCamel(String snakeCase) {
return Pattern.compile("_([a-z])").matcher(snakeCase)
.replaceAll(m -> m.group(1).toUpperCase());
}
// snakeToCamel("user_name") → "userName"

6. 密码强度校验#

public static boolean isStrongPassword(String password) {
// 至少8位,包含大小写字母、数字、特殊字符
String regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
return password.matches(regex);
}

最佳实践#

1. 缓存Pattern#

频繁使用的正则一定要编译成Pattern对象缓存起来,避免重复编译:

// 好:缓存Pattern
private static final Pattern EMAIL_PATTERN =
Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");
public static boolean isValidEmail(String email) {
return EMAIL_PATTERN.matcher(email).matches();
}

2. 用非捕获组提高性能#

不需要捕获内容的时候,用(?:...)代替(...)

// 捕获组(会保存匹配结果,额外开销)
Pattern.compile("(abc|def)");
// 非捕获组(不保存,性能更好)
Pattern.compile("(?:abc|def)");

3. 小心正则回溯#

复杂的正则可能导致大量回溯,极端情况下会CPU飙升。有次我写了一个复杂的正则去匹配日志,结果线上CPU直接飙到100%,就是因为正则回溯:

// 危险!这个正则可能导致指数级回溯
String regex = "(a+)+b";
String text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
// 匹配失败时,会尝试所有可能的分组组合,CPU直接爆炸
// 修复:用更具体的模式
String regex = "a+b";

4. 转义字符#

Java字符串里反斜杠本身就是转义字符,所以写正则里的\d要写成\\d

// 错误
String regex = "\d+"; // Java编译错误:\d不是有效的转义字符
// 正确
String regex = "\\d+";

实战贴士#

  1. 缓存PatternPattern.compile()有开销,频繁用的正则定义为static final
  2. 注意贪婪匹配:默认是贪婪的,处理HTML等嵌套结构时记得用*?非贪婪
  3. 测试先行:复杂的正则先用在线工具(如regex101.com)测试好再写代码
  4. 警惕回溯灾难:避免(a+)+b这种嵌套量词的模式
  5. Java字符串转义:记得\d要写成\\d\要写成\\\\

常见坑#

  • 贪婪匹配导致匹配过多<div>.*</div>会匹配整个字符串而不是单个标签。用.*?解决。
  • 转义字符搞错:忘写双反斜杠,编译直接报错。
  • 正则回溯导致CPU飙升:复杂正则匹配长字符串时可能卡死,这个我踩过,线上事故现场很惨烈。
  • 边界匹配忘了email.matches("\\w+@\\w+\\.com")会匹配abc@def.comxyz,因为matches没有用^$也能匹配子串。实际上matches()方法本身就是全匹配,所以这个例子不太对,但find()就需要注意边界。
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

部分信息可能已经过时

目录