1. Java正则表达式基础概念
正则表达式(Regular Expression)是处理字符串的强大工具,它通过特定的语法规则来描述、匹配一系列符合某个句法规则的字符串。在Java中,正则表达式主要通过java.util.regex包来实现,这个包提供了Pattern和Matcher两个核心类。
Pattern类表示一个编译后的正则表达式模式,它是不可变且线程安全的。我们通常使用Pattern.compile()方法来将字符串形式的正则表达式编译成Pattern对象。例如:
java复制Pattern pattern = Pattern.compile("a*b");
Matcher类则负责对输入字符串进行解释和匹配操作。通过调用Pattern.matcher()方法可以创建一个Matcher对象:
java复制Matcher matcher = pattern.matcher("aaaaab");
boolean matches = matcher.matches(); // 返回true
Java正则表达式的基本匹配方法包括:
- matches():整个字符串是否匹配正则表达式
- find():在字符串中查找下一个匹配的子序列
- lookingAt():从字符串开头开始匹配
提示:在实际开发中,如果只需要一次匹配,可以直接使用String类的matches()方法,它内部会创建Pattern和Matcher对象。
2. Java正则表达式语法详解
2.1 基本元字符
Java正则表达式支持丰富的元字符,以下是常用的基本元字符:
.:匹配任意单个字符(除行终止符)\d:匹配数字,等价于[0-9]\D:匹配非数字,等价于[^0-9]\s:匹配空白字符(空格、制表符、换行等)\S:匹配非空白字符\w:匹配单词字符(字母、数字、下划线)\W:匹配非单词字符
2.2 量词
量词用于指定匹配字符的数量:
*:零次或多次+:一次或多次?:零次或一次{n}:恰好n次{n,}:至少n次{n,m}:n到m次
2.3 字符类
用方括号[]定义字符类,匹配其中任意一个字符:
[abc]:匹配a、b或c[^abc]:匹配除了a、b、c之外的字符[a-zA-Z]:匹配任意字母[0-9]:匹配任意数字
2.4 边界匹配
^:匹配行首$:匹配行尾\b:匹配单词边界\B:匹配非单词边界
3. Java正则表达式高级特性
3.1 分组和捕获
圆括号()用于创建分组,分组可以用于:
- 将多个字符作为一个单元处理
- 捕获匹配的子序列
- 后向引用
java复制Pattern pattern = Pattern.compile("(\\d{3})-(\\d{3})-(\\d{4})");
Matcher matcher = pattern.matcher("123-456-7890");
if (matcher.matches()) {
String areaCode = matcher.group(1); // "123"
String exchange = matcher.group(2); // "456"
String lineNumber = matcher.group(3); // "7890"
}
3.2 非捕获分组
使用(?:...)语法可以创建非捕获分组,它只用于分组而不捕获匹配内容:
java复制Pattern pattern = Pattern.compile("(?:Mr|Ms|Mrs)\\.\\s(\\w+)");
Matcher matcher = pattern.matcher("Mr. Smith");
if (matcher.find()) {
String name = matcher.group(1); // "Smith"
}
3.3 预查断言
Java支持四种预查断言:
(?=...):正向肯定预查(?!...):正向否定预查(?<=...):反向肯定预查(?<!...):反向否定预查
例如,匹配后面跟着"USD"的数字:
java复制Pattern pattern = Pattern.compile("\\d+(?= USD)");
Matcher matcher = pattern.matcher("100 USD");
if (matcher.find()) {
System.out.println(matcher.group()); // 输出"100"
}
3.4 模式标志
Pattern类支持多种模式标志,可以通过|组合使用:
Pattern.CASE_INSENSITIVE:忽略大小写Pattern.MULTILINE:多行模式(影响^和$的行为)Pattern.DOTALL:点号匹配所有字符包括行终止符Pattern.UNICODE_CASE:启用Unicode感知的大小写折叠
java复制Pattern pattern = Pattern.compile("^hello", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
4. Java正则表达式实战应用
4.1 常见验证场景
- 邮箱验证:
java复制String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
boolean isValid = "example@domain.com".matches(emailRegex);
- 手机号验证(中国):
java复制String phoneRegex = "^1[3-9]\\d{9}$";
boolean isValid = "13812345678".matches(phoneRegex);
- 密码强度验证(至少8位,包含大小写字母和数字):
java复制String passwordRegex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$";
boolean isStrong = "Password123".matches(passwordRegex);
4.2 字符串提取
从文本中提取所有URL:
java复制String text = "Visit https://www.example.com and http://test.org for more info";
String urlRegex = "https?://(?:[\\w-]+\\.)+[\\w-]+(?:/[\\w-./?%&=]*)?";
Pattern pattern = Pattern.compile(urlRegex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println(matcher.group());
}
4.3 字符串替换
将日期格式从"MM/DD/YYYY"改为"YYYY-MM-DD":
java复制String dateStr = "12/31/2023";
String newDate = dateStr.replaceAll("(\\d{2})/(\\d{2})/(\\d{4})", "$3-$1-$2");
// 结果:"2023-12-31"
4.4 文本分割
使用正则表达式分割字符串:
java复制String text = "apple,orange,,banana, grape";
String[] fruits = text.split("\\s*,\\s*");
// 结果:["apple", "orange", "", "banana", "grape"]
5. Java正则表达式性能优化
5.1 预编译Pattern对象
频繁使用的正则表达式应该预编译为Pattern对象:
java复制// 不好的做法:每次调用都重新编译
for (String input : inputs) {
boolean matches = input.matches(regex);
}
// 好的做法:预编译Pattern
Pattern pattern = Pattern.compile(regex);
for (String input : inputs) {
Matcher matcher = pattern.matcher(input);
boolean matches = matcher.matches();
}
5.2 避免贪婪匹配
贪婪量词(如*和+)会导致回溯问题,在可能的情况下使用非贪婪量词(*?和+?):
java复制// 贪婪匹配
Pattern greedy = Pattern.compile("<.*>");
Matcher m1 = greedy.matcher("<h1>title</h1>");
m1.find(); // 匹配整个"<h1>title</h1>"
// 非贪婪匹配
Pattern reluctant = Pattern.compile("<.*?>");
Matcher m2 = reluctant.matcher("<h1>title</h1>");
m2.find(); // 只匹配"<h1>"
5.3 使用字符类替代选择分支
选择分支|性能较差,能用字符类替代时应优先使用字符类:
java复制// 较差性能
Pattern slow = Pattern.compile("red|green|blue");
// 更好性能
Pattern fast = Pattern.compile("[rgb][ree][dln][e]?");
5.4 边界约束
添加适当的边界约束可以减少不必要的匹配尝试:
java复制// 没有边界约束
Pattern unbound = Pattern.compile("\\d{4}");
// 添加边界约束
Pattern bound = Pattern.compile("\\b\\d{4}\\b");
6. Java正则表达式常见问题与解决方案
6.1 特殊字符转义
正则表达式中的特殊字符(如., *, +, ?, $, ^, [, ], (, ), {, }, |, \)需要使用\\进行转义:
java复制// 匹配"file.txt"
Pattern pattern = Pattern.compile("file\\.txt");
6.2 匹配多行文本
默认情况下,^和$只匹配整个输入的开头和结尾。要匹配每行的开头和结尾,需要使用Pattern.MULTILINE标志:
java复制String text = "line1\nline2\nline3";
Pattern pattern = Pattern.compile("^line", Pattern.MULTILINE);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println(matcher.group()); // 输出"line"三次
}
6.3 处理Unicode字符
Java正则表达式默认支持Unicode,但要正确处理Unicode大小写折叠需要Pattern.UNICODE_CASE标志:
java复制Pattern pattern = Pattern.compile("ß", Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE);
boolean matches = pattern.matcher("SS").matches(); // true
6.4 正则表达式与String方法
String类提供了几个便捷的正则表达式方法:
- matches():检查整个字符串是否匹配正则表达式
- split():使用正则表达式分割字符串
- replaceAll():使用正则表达式替换所有匹配项
- replaceFirst():使用正则表达式替换第一个匹配项
java复制String phone = "123-456-7890";
boolean valid = phone.matches("\\d{3}-\\d{3}-\\d{4}");
String[] parts = phone.split("-");
String noDashes = phone.replaceAll("-", "");
7. Java正则表达式高级技巧
7.1 命名捕获组
Java 7+支持命名捕获组,语法为(?<name>...):
java复制Pattern pattern = Pattern.compile("(?<area>\\d{3})-(?<exchange>\\d{3})-(?<line>\\d{4})");
Matcher matcher = pattern.matcher("123-456-7890");
if (matcher.matches()) {
String area = matcher.group("area"); // "123"
String exchange = matcher.group("exchange"); // "456"
String line = matcher.group("line"); // "7890"
}
7.2 重置Matcher对象
Matcher对象可以重复使用,通过reset()方法:
java复制Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("123");
matcher.matches(); // true
matcher.reset("abc");
matcher.matches(); // false
7.3 使用Scanner类
Scanner类可以方便地使用正则表达式从输入中查找和提取数据:
java复制String input = "1 fish 2 fish red fish blue fish";
Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*");
System.out.println(s.nextInt()); // 1
System.out.println(s.nextInt()); // 2
System.out.println(s.next()); // "red"
System.out.println(s.next()); // "blue"
s.close();
7.4 正则表达式与Stream API
Java 8+可以将正则表达式与Stream API结合使用:
java复制Pattern pattern = Pattern.compile(",");
String[] result = pattern.splitAsStream("a,b,c,d")
.map(String::toUpperCase)
.toArray(String[]::new);
// 结果:["A", "B", "C", "D"]
8. Java正则表达式在面试中的常见问题
8.1 基础概念问题
-
Pattern和Matcher类的区别是什么?
- Pattern表示编译后的正则表达式,是不可变且线程安全的
- Matcher用于对输入字符串执行匹配操作,包含匹配状态
-
贪婪匹配和懒惰匹配的区别?
- 贪婪匹配会尽可能多地匹配字符(
*,+) - 懒惰匹配会尽可能少地匹配字符(
*?,+?)
- 贪婪匹配会尽可能多地匹配字符(
8.2 编码实现问题
- 编写验证电子邮件地址的正则表达式:
java复制String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
- 编写从HTML中提取所有链接的正则表达式:
java复制String htmlRegex = "<a\\s+(?:[^>]*?\\s+)?href=\"([^\"]*)\"";
8.3 性能优化问题
-
如何优化频繁使用的正则表达式?
- 预编译Pattern对象并重用
- 避免在循环中重复编译正则表达式
-
什么情况下正则表达式会导致性能问题?
- 复杂的回溯
- 没有边界的匹配
- 过多的选择分支
8.4 实际应用问题
-
如何使用正则表达式验证密码强度?
- 至少8位,包含大小写字母和数字:
"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$"
- 至少8位,包含大小写字母和数字:
-
如何使用正则表达式格式化电话号码?
- 将"1234567890"格式化为"123-456-7890":
"$1-$2-$3".replaceAll("(\\d{3})(\\d{3})(\\d{4})")
- 将"1234567890"格式化为"123-456-7890":
9. Java正则表达式与其他语言的比较
9.1 与Python正则表达式的区别
-
语法差异:
- Java需要双反斜杠转义(
\\dvs Python的\d) - Java不支持
(?P<name>...)命名组语法
- Java需要双反斜杠转义(
-
方法差异:
- Python的re模块提供更简洁的顶级函数(re.match(), re.search())
- Java需要显式创建Pattern和Matcher对象
9.2 与JavaScript正则表达式的区别
-
标志差异:
- JavaScript使用
/pattern/flags语法(如/test/i) - Java使用Pattern.compile()的第二个参数指定标志
- JavaScript使用
-
方法差异:
- JavaScript正则表达式是对象字面量
- Java需要显式的Pattern和Matcher类
9.3 与Perl正则表达式的区别
-
功能差异:
- Perl的正则表达式功能最丰富
- Java缺少一些高级特性如条件表达式
-
语法差异:
- Perl使用
=~操作符进行匹配 - Java使用Matcher对象的方法
- Perl使用
10. Java正则表达式最佳实践
10.1 代码组织建议
- 将常用的正则表达式定义为常量:
java复制public class RegexConstants {
public static final String EMAIL = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
public static final String PHONE = "^1[3-9]\\d{9}$";
// 其他常用正则表达式...
}
- 为复杂正则表达式添加注释:
java复制String regex =
"^" // 字符串开始
+ "(?=.*[a-z])" // 至少一个小写字母
+ "(?=.*[A-Z])" // 至少一个大写字母
+ "(?=.*\\d)" // 至少一个数字
+ "[a-zA-Z\\d]" // 只允许字母和数字
+ "{8,}" // 至少8个字符
+ "$"; // 字符串结束
10.2 测试策略
- 为重要的正则表达式编写单元测试:
java复制@Test
public void testEmailRegex() {
assertTrue("valid@example.com".matches(RegexConstants.EMAIL));
assertFalse("invalid@".matches(RegexConstants.EMAIL));
}
- 使用边界值测试正则表达式:
java复制@Test
public void testPhoneRegexBoundaries() {
assertTrue("13812345678".matches(RegexConstants.PHONE));
assertFalse("12812345678".matches(RegexConstants.PHONE)); // 2开头无效
assertFalse("138123456789".matches(RegexConstants.PHONE)); // 过长
}
10.3 性能监控
- 监控正则表达式的执行时间:
java复制long start = System.nanoTime();
boolean result = input.matches(regex);
long duration = System.nanoTime() - start;
if (duration > THRESHOLD) {
logger.warn("Slow regex execution: " + regex);
}
- 使用Profiler工具分析正则表达式性能:
- VisualVM
- Java Mission Control
- YourKit
10.4 安全考虑
- 防范正则表达式拒绝服务(ReDoS)攻击:
java复制// 避免使用可能导致灾难性回溯的正则表达式
String dangerousRegex = "(a+)+"; // 对"aaaaaaaaX"会导致大量回溯
// 设置超时(Java 9+)
Pattern pattern = Pattern.compile(regex).withTimeout(Duration.ofSeconds(1));
- 对用户提供的正则表达式进行验证和限制:
java复制public Pattern safeCompile(String userRegex) throws RegexSecurityException {
if (userRegex.length() > MAX_LENGTH) {
throw new RegexSecurityException("Regex too long");
}
if (userRegex.contains("(a+)+")) {
throw new RegexSecurityException("Potentially dangerous pattern");
}
return Pattern.compile(userRegex);
}
在实际项目中,我发现合理使用正则表达式可以极大简化字符串处理逻辑,但过度依赖正则表达式也会导致代码难以维护。对于特别复杂的匹配规则,考虑使用专门的解析器(如ANTLR)可能是更好的选择。
