正则表达式详解与 Java 实践
在一次面试中,面试官提出了以下关于正则表达式的问题。本文将逐一解答,并结合一个具体的 Java 场景------从文本中提取并验证电子邮件地址------进行代码实践,同时针对"零宽断言"和复杂模式的疑问进行详细说明。
1. 什么是正则表达式?
正则表达式(Regular Expression,简称 regex)是一种用于匹配、查找和操作文本的强大工具。它通过定义特定的模式(pattern),来描述需要匹配的字符串规则。
场景实践 :假设我们需要从一段文本中提取电子邮件地址,正则表达式可以帮助我们定义邮箱的模式,例如 username@domain.com。
2. 简述正则表达式的字符类
字符类(Character Class)用于匹配一组特定字符中的任意一个,用方括号 [] 表示。例如,[abc] 表示匹配 a、b 或 c 中的一个字符。
场景实践 :在邮箱中,用户名可能包含字母、数字或下划线,我们可以用 [a-zA-Z0-9_] 来匹配这些字符。
3. 正则表达式有哪些预定义的字符类?
预定义字符类是正则表达式中内置的简写形式,用于表示常见的字符集合,例如:
\d:匹配任意数字[0-9]\w:匹配任意字母、数字或下划线[a-zA-Z0-9_]\s:匹配任意空白字符(空格、制表符等).:匹配除换行符外的任意字符
场景实践 :在邮箱模式中,\w可以用来匹配用户名中的字符,例如[\w.-]表示用户名可以包含字母、数字、下划线、点或横杠。
4. 正则表达式有哪些重复类?
重复类(Quantifiers)用于指定字符或模式的重复次数,包括:
*:0 次或多次+:1 次或多次?:0 次或 1 次{n}:恰好 n 次{n,}:至少 n 次{n,m}:n 到 m 次
场景实践 :邮箱用户名可能包含多个字符,我们用\w+表示用户名至少包含一个字母、数字或下划线。
5. 正则表达式有哪些反义类?
反义类(Negated Character Class)表示不匹配某些字符,通常用 ^ 在字符类中表示,例如:
[^abc]:匹配除a、b、c外的任意字符\D:匹配非数字字符[^\d]\W:匹配非字母、数字或下划线的字符[^\w]\S:匹配非空白字符[^\s]
场景实践 :在邮箱验证中,我们可能需要确保域名部分不包含空白字符,可以用\S。
6. 正则表达式分组是什么?
分组(Grouping)使用括号 () 将正则表达式的一部分组合起来,可以:
- 捕获匹配的内容供后续使用(通过组编号或命名组)
- 对模式应用量词
场景实践 :在邮箱中,我们可以用(\w+)分组捕获用户名部分,之后可以通过组号提取它。
7. 正则表达式零宽断言是什么?
零宽断言(Zero-width Assertion)是一种不占用字符宽度的匹配条件,用于指定匹配位置的上下文。它像是站在字符串中的某个点上,向前或向后"偷看"一眼,判断是否符合要求,但不会把"偷看"的内容算进匹配结果。包括:
(?=...):正向肯定预查,检查后面是否符合某个模式(?!...):正向否定预查,检查后面不符合某个模式(?<=...):反向肯定预查,检查前面是否符合某个模式(?<!...):反向否定预查,检查前面不符合某个模式
通俗例子 :假设你在一条街上找房子,要求房子右边必须有公园(但公园不算在房子范围内),这就是正向肯定预查 (?=公园):
- 输入:
房子A公园 房子B商场 房子C公园 - 匹配:
房子A和房子C,因为它们右边有公园,但"公园"不包含在结果中。
场景实践 :在邮箱提取中,我们用 (?=\.com$) 确保匹配的邮箱以 .com 结尾。例如:
- 输入:
alice@domain.com bob@xyz.org - 模式:
@(\w+)\.(\w+)(?=\.com$) - 结果:只匹配
domain,因为它后面是.com,而xyz后面是.org,不满足条件。
为什么叫"零宽"? 因为它只检查条件,不消耗字符。比如在alice@domain.com中,(?=\.com$)检查@domain后的位置,确认后面是.com,但.com不会被包括在最终匹配结果中。
8. 正则表达式的贪婪与懒惰是什么?
正则表达式的匹配默认是贪婪模式(Greedy) ,即尽可能多地匹配字符;通过在量词后加 ? 可以变为懒惰模式(Lazy),即尽可能少地匹配。
- 贪婪:
.*会匹配整个字符串 - 懒惰:
.*?只匹配到满足条件的最短部分
场景实践 :在提取多个邮箱时,贪婪模式可能匹配过多的内容,而懒惰模式.*?可以更精确地定位每个邮箱。
Java 代码实践:提取并验证电子邮件地址
以下是一个 Java 示例,展示如何使用正则表达式从文本中提取电子邮件地址,并验证其格式,同时对比简单模式和复杂模式的差异。
java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EmailExtractor {
public static void main(String[] args) {
// 示例文本
String text = "联系方式: alice123@domain.com, alice.smith-jr@xyz.com, bob@xyz.org";
// 简单模式:只用 \w+
String simplePattern = "\\w+@\\w+\\.\\w{2,4}";
System.out.println("简单模式匹配:");
matchEmails(text, simplePattern);
// 复杂模式:支持 . 和 -,并用零宽断言限制 .com
String complexPattern = "(\\w+([.-]?\\w+)*)@(\\w+([.-]?\\w+)*\\.\\w{2,4})(?=\\.com$)";
System.out.println("\n复杂模式匹配(仅限 .com):");
matchEmails(text, complexPattern);
}
private static void matchEmails(String text, String pattern) {
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(text);
while (m.find()) {
String email = m.group(0); // 完整邮箱
String username = m.group(1); // 用户名部分
String domain = m.group(3); // 域名部分
System.out.printf("邮箱: %s, 用户名: %s, 域名: %s%n", email, username, domain);
}
}
}
代码解析
-
简单模式
\w+@的局限性:- 只适合基本格式(如
alice123@domain.com)。 - 对于带
.或-的用户名(如alice.smith-jr),会失败。
- 只适合基本格式(如
-
复杂模式
(\w+([.-]?\w+)*)@(\w+([.-]?\\w+)*\.\\w{2,4})(?=\\.com$):- 用户名部分
(\w+([.-]?\w+)*):\w+:匹配至少一个字母、数字或下划线(如alice)。[.-]?\w+:匹配可选的.或-,后跟字母、数字或下划线(如.smith或-jr)。*:允许出现多次(如alice.smith-jr)。
- 域名部分
(\w+([.-]?\\w+)*\.\\w{2,4}):- 类似用户名,支持复杂域名(如
sub.domain.com)。 \.\\w{2,4}:匹配顶级域名(如.com),长度 2-4 个字符。
- 类似用户名,支持复杂域名(如
- 零宽断言
(?=\.com$):确保邮箱以.com结尾,不匹配.org等。
- 用户名部分
-
为什么不用简单的
\w+?对于
alice123@domain.com,\w+确实够用。但真实邮箱可能包含.或-(如alice.smith-jr@xyz.com),简单模式会漏掉这些情况。复杂模式更灵活,能覆盖更多场景。
输出示例
java
简单模式匹配:
邮箱: alice123@domain.com, 用户名: alice123, 域名: domain.com
邮箱: alice@xyz.com, 用户名: alice, 域名: xyz.com
邮箱: bob@xyz.org, 用户名: bob, 域名: xyz.org
复杂模式匹配(仅限 .com):
邮箱: alice123@domain.com, 用户名: alice123, 域名: domain.com
总结
正则表达式是处理文本的利器,掌握其字符类、重复类、反义类、分组、零宽断言以及贪婪/懒惰模式,可以应对复杂的匹配需求。通过 Java 的 Pattern 和 Matcher,我们可以轻松实现文本提取和验证功能。零宽断言为我们提供了位置检查的灵活性,而复杂模式则确保了更广泛的适用性。