849 字
2 分钟
Java正则表达式详解
正则表达式学的时候觉得像天书,各种符号看得眼花缭乱。但用多了才发现它是一把锋利的瑞士军刀——校验手机号、提取日志关键信息,一行正则能顶几十行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>", "");// 结果:""(全部被替换掉了)
// 非贪婪:只匹配第一个divtext.replaceAll("<div>.*?</div>", "");// 结果:<div>内容2</div>分组
| 表达式 | 含义 |
|---|---|
(xyz) | 捕获组 |
(?:xyz) | 非捕获组(不保存匹配结果,性能更好) |
(?<name>xyz) | 命名捕获组 |
Java中使用正则
Pattern 和 Matcher
String regex = "\\d{3}-\\d{3}-\\d{4}"; // 匹配 xxx-xxx-xxxxString 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对象缓存起来,避免重复编译:
// 好:缓存Patternprivate 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+";实战贴士
- 缓存Pattern:
Pattern.compile()有开销,频繁用的正则定义为static final - 注意贪婪匹配:默认是贪婪的,处理HTML等嵌套结构时记得用
*?非贪婪 - 测试先行:复杂的正则先用在线工具(如regex101.com)测试好再写代码
- 警惕回溯灾难:避免
(a+)+b这种嵌套量词的模式 - Java字符串转义:记得
\d要写成\\d,\要写成\\\\
常见坑
- 贪婪匹配导致匹配过多:
<div>.*</div>会匹配整个字符串而不是单个标签。用.*?解决。 - 转义字符搞错:忘写双反斜杠,编译直接报错。
- 正则回溯导致CPU飙升:复杂正则匹配长字符串时可能卡死,这个我踩过,线上事故现场很惨烈。
- 边界匹配忘了:
email.matches("\\w+@\\w+\\.com")会匹配abc@def.comxyz,因为matches没有用^和$也能匹配子串。实际上matches()方法本身就是全匹配,所以这个例子不太对,但find()就需要注意边界。
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时
相关文章 智能推荐
1
Java运算符与表达式详解
Java ++i和i++的区别是多少Java新手的噩梦?还有位运算虽然平时用得少,但在权限系统和性能优化场景下简直是神器。
2
Java注解详解
Java 刚学Java时觉得注解不就是几个@符号嘛,后来用Spring Boot才发现注解能玩出这么多花样——从编译检查到运行时注入,注解远不止看起来那么简单。
3
Java NIO详解
Java NIO的Selector模型让我第一次感受到了单线程处理万级连接的魅力。相比传统IO的阻塞读写,Channel加Buffer的组合简直是为高性能网络编程量身定做。
4
Java反射机制详解
Java 反射是Java高级特性中最让我着迷的一个——运行时能看见自己。工作中用Spring的依赖注入时总在想它是怎么做到的,后来才明白反射就是那个幕后推手。
5
Java Stream API详解
Java Stream API让Java从命令式编程迈向了函数式编程的大门。从filter/map/reduce开始,你会爱上这种链式处理集合数据的写法。






