文章目录
- 1. 正则表达式基础
- 1.1 什么是正则表达式
- 1.2 为什么需要学习正则表达式
- 1.3 Java中的正则表达式支持
- 2. 正则表达式语法
- 2.1 基本匹配
- 2.2 元字符
- 2.2.1 常用元字符
- 2.2.2 转义字符
- 2.2.3 字符类
- 2.2.4 预定义字符类
- 2.2.5 量词
- 2.3 贪婪与非贪婪匹配
- 2.4 分组与捕获
- 2.4.1 命名分组
- 2.4.2 非捕获分组
- 2.5 边界匹配器
- 3. 高级正则表达式特性
- 3.1 零宽断言(Look-around)
- 3.2 条件匹配
- 3.3 模式标志
- 3.4 反向引用
- 3.5 递归匹配
- 4. Java中的正则表达式API
- 4.1 使用Pattern和Matcher类
- 4.2 重要的Pattern方法
- 4.3 重要的Matcher方法
- 4.4 String类中的正则表达式方法
- 4.5 特殊替换序列
- 5. 常见正则表达式示例与解析
- 5.1 数据验证正则表达式
- 电子邮件验证
- 电话号码验证(美国格式)
- URL验证
- 日期验证(YYYY-MM-DD格式)
- 密码强度验证
- 5.2 文本处理正则表达式
- 提取所有HTML标签
- 提取所有URL
- 删除HTML标签
- 格式化电话号码
- 5.3 高级示例
- 解析CSV数据
- 提取文本中的所有日期
- 6. 正则表达式在Java中的实际应用
- 6.1 处理配置文件
- 6.2 日志解析
- 6.3 网页爬虫
- 6.4 表单验证
- 6.5 文本替换处理
- 7. 正则表达式性能优化
- 7.1 常见性能问题
- 7.2 优化技巧
- 7.2.1 缓存编译后的Pattern对象
- 7.2.2 使用非捕获组
- 7.2.3 优化量词
- 7.2.4 使用原子组
- 7.2.5 避免过度使用点通配符和贪婪量词
- 7.3 性能测试
- 8. 正则表达式调试与故障排除
- 8.1 常见问题与解决方案
- 8.1.1 匹配失败
- 8.1.2 性能问题
- 8.2 正则表达式可视化工具
- 8.3 单元测试正则表达式
- 9. 正则表达式的局限性与替代方案
- 9.1 何时不使用正则表达式
- 9.2 替代方案
- 9.2.1 专用解析器
- 9.2.2 字符串处理库
- 9.2.3 状态机
- 10. 总结与最佳实践
- 10.1 正则表达式的核心概念回顾
- 10.2 正则表达式最佳实践
- 10.3 持续学习资源
- 附录:正则表达式速查表
- 元字符
- 字符类
- 预定义字符类
- 量词
- 零宽断言
- 其他
1. 正则表达式基础
1.1 什么是正则表达式
正则表达式(Regular Expression,简称RegEx)是一种用于描述字符串匹配模式的表达式语言,是一种强大的文本处理工具。通过使用特定的语法规则,我们可以:
- 在文本中查找特定的模式
- 验证字符串是否符合特定格式(如邮箱、电话号码等)
- 替换文本中的特定内容
- 从文本中提取特定的信息
正则表达式被广泛应用于文本处理、数据验证、爬虫开发、日志分析等场景,是程序员必备的技能之一。
1.2 为什么需要学习正则表达式
假设你需要验证用户输入的是否是有效的电子邮件地址。不使用正则表达式,你可能需要编写很多复杂的条件判断代码:
boolean isValidEmail(String email) {// 检查是否包含@符号if (!email.contains("@")) return false;// 检查@前后是否有内容String[] parts = email.split("@");if (parts.length != 2) return false;if (parts[0].length() == 0 || parts[1].length() == 0) return false;// 检查域名部分是否包含至少一个点if (!parts[1].contains(".")) return false;// 检查用户名部分是否有效(不包含特殊字符等)// ...更多复杂的检查return true;
}
而使用正则表达式,只需一行代码:
boolean isValidEmail(String email) {return email.matches("^[\\w.-]+@([\\w-]+\\.)+[\\w-]{2,4}$");
}
正则表达式不仅使代码更简洁,而且通常比手写的字符串处理逻辑更高效、更可靠。
1.3 Java中的正则表达式支持
Java通过java.util.regex
包提供了对正则表达式的支持,主要包含以下核心类:
Pattern
:编译后的正则表达式对象Matcher
:执行匹配操作的引擎PatternSyntaxException
:表示正则表达式语法错误的异常
此外,String
类也提供了几个直接使用正则表达式的方法:
matches()
:判断字符串是否匹配正则表达式split()
:使用正则表达式分割字符串replaceAll()
和replaceFirst()
:使用正则表达式替换字符串内容
2. 正则表达式语法
2.1 基本匹配
最简单的正则表达式就是普通字符,它们表示字面值匹配:
String text = "Hello, World!";
boolean matches = text.matches("Hello, World!"); // 返回true
这里的正则表达式"Hello, World!"
只匹配完全相同的字符串。
2.2 元字符
元字符是正则表达式中具有特殊含义的字符,是正则表达式强大功能的基础。
2.2.1 常用元字符
元字符 | 描述 | 示例 |
---|---|---|
. | 匹配任意单个字符(除了换行符) | "a.c" 匹配 “abc”, “adc”, “a1c” 等 |
^ | 匹配字符串的开始 | "^Hello" 匹配以Hello开头的字符串 |
$ | 匹配字符串的结束 | "World$" 匹配以World结尾的字符串 |
* | 匹配前面的表达式0次或多次 | "a*" 匹配 “”, “a”, “aa”, “aaa” 等 |
+ | 匹配前面的表达式1次或多次 | "a+" 匹配 “a”, “aa”, “aaa” 等 |
? | 匹配前面的表达式0次或1次 | "colou?r" 匹配 “color” 和 “colour” |
\ | 转义字符 | "\\." 匹配 “.” 字符本身 |
Java示例:
// 匹配任意字符
String text = "cat";
boolean matches = text.matches("c.t"); // 返回true,因为"cat"符合"c任意字符t"的模式// 匹配开头和结尾
String text1 = "Hello, World!";
boolean startsWithHello = text1.matches("^Hello.*"); // 返回true,因为字符串以Hello开头
boolean endsWithWorld = text1.matches(".*World!$"); // 返回true,因为字符串以World!结尾// 使用量词
String text2 = "abbbc";
boolean matches2 = text2.matches("ab*c"); // 返回true,因为"abbbc"包含"a"后跟多个"b"再跟"c"
2.2.2 转义字符
在Java字符串和正则表达式中,反斜杠\
都是特殊字符,因此在Java代码中表示正则表达式的反斜杠时,需要使用两个反斜杠\\
:
// 匹配字面上的点号(.)
String text = "example.com";
boolean matches = text.matches("example\\.com"); // 错误:在Java字符串中,\会被解释为转义字符
boolean correctMatches = text.matches("example\\\\.com"); // 错误:过多的反斜杠
boolean properMatches = text.matches("example\\.com"); // 正确:两个反斜杠表示正则表达式中的一个反斜杠
2.2.3 字符类
字符类允许匹配一组字符中的任意一个:
字符类 | 描述 | 示例 |
---|---|---|
[abc] | 匹配方括号内的任意一个字符 | [abc] 匹配 “a”, “b”, 或 “c” |
[^abc] | 匹配除了方括号内的任意一个字符 | [^abc] 匹配任何除了 “a”, “b”, 或 “c” 的字符 |
[a-z] | 匹配指定范围内的任意一个字符 | [a-z] 匹配任何小写字母 |
[a-zA-Z] | 可以组合多个范围 | [a-zA-Z] 匹配任何英文字母 |
Java示例:
// 匹配字符集中的任意字符
String text = "cat";
boolean matches = text.matches("[bcd]at"); // 返回false,因为"cat"的第一个字符不在[bcd]中
text = "bat";
matches = text.matches("[bcd]at"); // 返回true// 匹配字符范围
String digit = "5";
boolean isDigit = digit.matches("[0-9]"); // 返回true,因为"5"在数字范围内// 匹配多个范围
String letter = "K";
boolean isLetter = letter.matches("[a-zA-Z]"); // 返回true,因为"K"是字母
2.2.4 预定义字符类
为了方便,正则表达式提供了一些预定义的字符类:
预定义类 | 描述 | 等价于 |
---|---|---|
\d | 匹配任意数字 | [0-9] |
\D | 匹配任意非数字 | [^0-9] |
\w | 匹配任意字母、数字或下划线 | [a-zA-Z0-9_] |
\W | 匹配任意非字母、数字或下划线 | [^a-zA-Z0-9_] |
\s | 匹配任意空白字符 | [ \t\n\r\f] |
\S | 匹配任意非空白字符 | [^ \t\n\r\f] |
在Java中使用这些预定义类时,需要使用双反斜杠:
// 检查是否为单个数字
String text = "7";
boolean isDigit = text.matches("\\d"); // 返回true// 检查是否由字母、数字和下划线组成
String username = "user_123";
boolean isValidUsername = username.matches("\\w+"); // 返回true// 检查是否包含空白字符
String hasSpace = "Hello World";
boolean containsSpace = hasSpace.matches(".*\\s.*"); // 返回true
2.2.5 量词
量词用于指定前面的表达式应该匹配多少次:
量词 | 描述 | 示例 |
---|---|---|
* | 匹配0次或多次 | a* 匹配 “”, “a”, “aa”, … |
+ | 匹配1次或多次 | a+ 匹配 “a”, “aa”, … |
? | 匹配0次或1次 | a? 匹配 “” 或 “a” |
{n} | 精确匹配n次 | a{3} 匹配 “aaa” |
{n,} | 匹配至少n次 | a{2,} 匹配 “aa”, “aaa”, … |
{n,m} | 匹配n到m次 | a{1,3} 匹配 “a”, “aa”, “aaa” |
Java示例:
// 精确匹配次数
String text = "aaa";
boolean matches = text.matches("a{3}"); // 返回true,因为正好有3个a// 匹配范围次数
String numbers = "123";
boolean isThreeDigits = numbers.matches("\\d{1,3}"); // 返回true,因为有1到3个数字// 组合使用
String phoneNumber = "123-456-7890";
boolean isValidPhone = phoneNumber.matches("\\d{3}-\\d{3}-\\d{4}"); // 返回true
2.3 贪婪与非贪婪匹配
默认情况下,量词是贪婪的,意味着它们会尽可能多地匹配字符:
String text = "abcdefg";
String pattern = "a.*f"; // 贪婪匹配
// 结果将匹配"abcdef",因为.*会尽可能多地匹配
可以通过在量词后添加?
使其变成非贪婪(懒惰)匹配:
String text = "abcdefg";
String pattern = "a.*?f"; // 非贪婪匹配
// 结果将匹配尽可能少的字符,只要能匹配到f就停止
Java示例:
String html = "<div>内容1</div><div>内容2</div>";// 贪婪匹配:匹配从第一个<div>到最后一个</div>的所有内容
Pattern greedyPattern = Pattern.compile("<div>.*</div>");
Matcher greedyMatcher = greedyPattern.matcher(html);
if (greedyMatcher.find()) {System.out.println("贪婪匹配: " + greedyMatcher.group()); // 输出:<div>内容1</div><div>内容2</div>
}// 非贪婪匹配:匹配尽可能短的内容
Pattern lazyPattern = Pattern.compile("<div>.*?</div>");
Matcher lazyMatcher = lazyPattern.matcher(html);
if (lazyMatcher.find()) {System.out.println("非贪婪匹配: " + lazyMatcher.group()); // 输出:<div>内容1</div>
}
2.4 分组与捕获
括号()
在正则表达式中用于分组,这不仅可以应用量词到整个组,还可以捕获匹配的文本:
String text = "John Smith";
Pattern pattern = Pattern.compile("(\\w+)\\s(\\w+)");
Matcher matcher = pattern.matcher(text);if (matcher.matches()) {String firstName = matcher.group(1); // "John"String lastName = matcher.group(2); // "Smith"System.out.println("First name: " + firstName);System.out.println("Last name: " + lastName);
}
2.4.1 命名分组
Java 7及以上版本支持命名捕获组,使代码更具可读性:
String text = "John Smith";
Pattern pattern = Pattern.compile("(?<firstName>\\w+)\\s(?<lastName>\\w+)");
Matcher matcher = pattern.matcher(text);if (matcher.matches()) {String firstName = matcher.group("firstName"); // "John"String lastName = matcher.group("lastName"); // "Smith"System.out.println("First name: " + firstName);System.out.println("Last name: " + lastName);
}
2.4.2 非捕获分组
如果只想使用括号进行分组,但不需要捕获匹配的文本,可以使用非捕获分组(?:...)
:
String text = "abc123def456";
Pattern pattern = Pattern.compile("(?:\\d+)([a-z]+)");
Matcher matcher = pattern.matcher(text);if (matcher.find()) {// matcher.group(1)将是"def",不会捕获数字部分System.out.println(matcher.group(1));
}
2.5 边界匹配器
边界匹配器不匹配实际字符,而是匹配位置:
边界 | 描述 |
---|---|
^ | 匹配行的开始 |
$ | 匹配行的结束 |
\b | 匹配单词边界(一个\w与一个\W之间的位置,或字符串的开始或结束) |
\B | 匹配非单词边界 |
Java示例:
// 匹配整个单词
String text = "This is a simple example";
Pattern pattern = Pattern.compile("\\bsimple\\b");
Matcher matcher = pattern.matcher(text);
boolean found = matcher.find(); // 返回true,因为"simple"是一个完整的单词// 匹配以特定文本开头的行
String multiLine = "First line\nSecond line\nThird line";
Pattern startPattern = Pattern.compile("^Second", Pattern.MULTILINE);
Matcher startMatcher = startPattern.matcher(multiLine);
boolean matchesStart = startMatcher.find(); // 返回true,因为有一行以"Second"开头
3. 高级正则表达式特性
3.1 零宽断言(Look-around)
零宽断言用于查找特定模式前后的位置,而不消耗任何字符:
- 零宽正向先行断言(?=pattern):断言当前位置后面跟着特定模式
- 零宽负向先行断言(?!pattern):断言当前位置后面不跟着特定模式
- 零宽正向后行断言(?<=pattern)&#x