正则表达式入门到实战
前言
正则表达式(Regular Expression)是处理字符串的强大工具,被誉为程序员必备的"瑞士军刀"。无论是表单验证、日志分析,还是数据清洗、URL处理,正则表达式都能发挥重要作用。本文将从零开始,循序渐进地讲解正则表达式的核心概念和实战技巧。
一、正则表达式基础
1.1 什么是正则表达式
正则表达式是一种描述字符模式的语法规则,用于在文本中查找、匹配、替换符合特定模式的字符串。在Java中,我们主要通过java.util.regex包中的Pattern和Matcher类来使用正则表达式。
1.2 系统架构
在Java中,正则表达式的工作流程如下:
- 输入字符串 - 待处理的文本内容
- Pattern编译器 - 将正则模式编译成内部状态机
- Matcher匹配器 - 执行具体的匹配操作
- 匹配结果 - 返回匹配结果和分组信息
// 基本用法示例
Pattern pattern = Pattern.compile("\\d+"); // 编译正则
Matcher matcher = pattern.matcher("abc123def"); // 创建匹配器
while (matcher.find()) { // 查找匹配
System.out.println(matcher.group()); // 输出: 123
}
1.3 核心语法
元字符
正则表达式由普通字符和元字符组成。元字符有特殊含义:
| 元字符 | 说明 | 示例 | 匹配结果 |
|---|---|---|---|
. | 任意单个字符 | a.c | abc, adc, a1c |
* | 零次或多次 | ab* | a, ab, abb |
+ | 一次或多次 | ab+ | ab, abb |
? | 零次或一次 | ab? | a, ab |
^ | 行首 | ^Hello | Hello World |
$ | 行尾 | World$ | Hello World |
字符类
[abc]- 匹配a、b或c[a-z]- 匹配任意小写字母[0-9]或\d- 匹配数字\w- 匹配单词字符(字母、数字、下划线)\s- 匹配空白字符
边界锚点
\b- 单词边界^- 字符串开头$- 字符串结尾
1.4 实战示例:常用验证
// 邮箱验证
String emailPattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
boolean isValidEmail = Pattern.matches(emailPattern, "test@example.com");
// 手机号验证(中国)
String phonePattern = "^1[3-9]\\d{9}$";
boolean isValidPhone = Pattern.matches(phonePattern, "13800138000");
// 身份证验证
String idPattern = "^\\d{17}[\\dXx]$";
boolean isValidIdCard = Pattern.matches(idPattern, "123456789012345678");
二、正则表达式进阶
2.1 贪婪 vs 非贪婪匹配
默认情况下,正则表达式是贪婪的,会尽可能多地匹配字符。使用*?、+?、??可以实现非贪婪匹配。
String text = "<div>内容1</div><div>内容2</div>";
// 贪婪匹配 - 匹配所有
Pattern greedy = Pattern.compile("<div>.*</div>");
// 结果: <div>内容1</div><div>内容2</div>
// 非贪婪匹配 - 匹配单个
Pattern lazy = Pattern.compile("<div>.*?</div>");
// 结果: <div>内容1</div>
2.2 零宽断言
零宽断言是一种特殊的匹配方式,它只判断位置,不消耗字符。
| 类型 | 语法 | 说明 |
|---|---|---|
| 正向先行断言 | (?=...) | 后面跟着... |
| 负向先行断言 | (?!...) | 后面不跟着... |
| 正向后行断言 | (?<=...) | 前面是... |
| 负向后行断言 | (?<!...) | 前面不是... |
// 匹配后面跟着数字的单词
String pattern = "\\w+(?=\\d)";
// apple1 banana2 cherry -> 匹配 apple, banana
2.3 分组与回溯引用
使用圆括号()可以创建分组,通过\1、\2等方式引用前面的分组。
// 匹配重复的单词
Pattern pattern = Pattern.compile("\\b(\\w+)\\s+\\1\\b");
// hello hello world -> 匹配 "hello hello"
// 匹配HTML标签对
Pattern htmlPattern = Pattern.compile("<(\\w+)>.*?</\\1>");
// <div>内容</div> -> 匹配成功
2.4 命名分组(Java 9+)
Pattern pattern = Pattern.compile(
"\\[(?<timestamp>[^\\]]+)\\]\\s+" +
"\\[(?<level>[^\\]]+)\\]"
);
Matcher matcher = pattern.matcher("[2024-03-05] [INFO] log message");
String timestamp = matcher.group("timestamp"); // 2024-03-05
String level = matcher.group("level"); // INFO
三、匹配流程
正则表达式的匹配过程遵循以下流程:
- 开始 - 编译正则模式
- 创建Matcher - 将模式应用到输入字符串
- 执行匹配 - 从字符串开头开始匹配
- 判断结果 - 匹配成功则返回结果,否则移动到下一个位置继续尝试
- 继续 - 直到匹配成功或字符串遍历完毕
四、性能优化
4.1 预编译正则表达式
频繁使用的正则表达式应该预编译,避免重复编译开销。
// 好的做法 - 预编译
public class Validator {
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();
}
}
// 不好的做法 - 每次编译
boolean isValid = Pattern.compile("...").matcher(email).matches();
4.2 避免回溯爆炸
避免使用嵌套量词,如(a+)+,这种模式在某些输入下会导致指数级的时间复杂度。
// 危险 - 可能导致回溯爆炸
Pattern dangerous = Pattern.compile("(a+)+");
// 安全 - 使用字符类
Pattern safe = Pattern.compile("a+");
五、最佳实践
| 实践 | 说明 |
|---|---|
| OK 预编译正则 | 频繁使用的正则应预编译 |
| OK 非贪婪匹配 | 用*?、+?代替*、+ |
| OK 避免回溯 | 避免嵌套量词 |
| OK 使用字符类 | 用[abc]代替a|b|c |
| OK 边界锚点 | 用^、$、\b限定匹配范围 |
| X 避免复杂 | 超长正则难以维护 |
| X 测试边界 | 特殊字符、空值都需要测试 |
六、开源框架中的应用
6.1 Spring MVC - 路径匹配
Spring的AntPathMatcher将Ant风格的路径模式转换为正则表达式进行匹配。
AntPathMatcher matcher = new AntPathMatcher();
boolean match = matcher.match("/api/**", "/api/users/123"); // true
6.2 MyBatis - 动态SQL解析
MyBatis使用正则表达式解析XML中的动态SQL标签。
<if test="name != null">AND name = #{name}</if>
6.3 Logback - 日志过滤
Logback使用正则表达式过滤特定模式的日志。
<!-- 只记录ERROR级别的日志 -->
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
<expression>level == ERROR</expression>
</evaluator>
</filter>
/**
* 示例4:Logback日志过滤
* 使用正则表达式匹配日志内容进行过滤
*/
public static class LogbackFilter {
private Pattern includePattern;
private Pattern excludePattern;
public LogbackFilter(String includeRegex, String excludeRegex) {
this.includePattern = includeRegex != null ? Pattern.compile(includeRegex) : null;
this.excludePattern = excludeRegex != null ? Pattern.compile(excludeRegex) : null;
}
public boolean shouldLog(String logLine) {
// 检查排除规则
if (excludePattern != null && excludePattern.matcher(logLine).find()) {
return false;
}
// 检查包含规则
if (includePattern == null || includePattern.matcher(logLine).find()) {
return true;
}
return false;
}
public static void demo() {
System.out.println("【框架4】Logback日志过滤");
// 只记录包含ERROR的日志,排除health check日志
LogbackFilter filter = new LogbackFilter(".*ERROR.*", ".*health.*");
String[] logs = {
"[ERROR] Connection failed",
"[INFO] Health check passed",
"[ERROR] Database timeout",
"[WARN] Slow query detected"
};
for (String log : logs) {
boolean logIt = filter.shouldLog(log);
System.out.printf(" %s %s%n", logIt ? "记录" : "忽略", log);
}
System.out.println();
}
}
6.4 Hibernate - 参数验证
Hibernate Validator的@Pattern注解使用正则表达式进行数据校验。
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
七、应用场景
7.1 表单验证
// 密码强度验证:至少8位,包含大小写字母和数字
Pattern passwordPattern = Pattern.compile(
"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$"
);
7.2 日志分析
// 解析日志格式 [时间] [级别] 类名 - 消息
Pattern logPattern = Pattern.compile(
"\\[(?<timestamp>[^\\]]+)\\]\\s+" +
"\\[(?<level>[^\\]]+)\\]\\s+" +
"(?<className>[A-Za-z0-9.$]+)\\s+-\\s+" +
"(?<message>.*)"
);
7.3 敏感信息脱敏
// 手机号脱敏: 13800138000 -> 138****8000
String masked = phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
// 身份证脱敏: 123456789012345678 -> 123456********5678
String masked = idCard.replaceAll("(\\d{6})\\d{8}(\\d{4})", "$1********$2");
7.4 SQL注入检测
// 检测常见SQL注入模式
Pattern[] injectionPatterns = {
Pattern.compile("(?i).*(union\\s+select)."),
Pattern.compile("(?i).*(or\\s+1\\s*=\\s*1)."),
Pattern.compile("(?i).*(drop\\s+table).")
};
public static boolean containsSqlInjection(String input) {
for (Pattern pattern : injectionPatterns) {
if (pattern.matcher(input).find()) {
return true;
}
}
return false;
}
八、总结
正则表达式是处理文本的强大工具,掌握它能大大提高开发效率。从基础的字符匹配到高级的零宽断言,从简单的表单验证到复杂的数据分析,正则表达式都能胜任。
记住以下要点:
-
预编译频繁使用的正则表达式
-
使用非贪婪匹配提高性能
-
添加边界锚点限定匹配范围
-
编写单元测试验证各种边界情况