正则表达式详解与 Java 实践
在一次面试中,面试官提出了以下关于正则表达式的问题。本文将逐一解答,并结合一个具体的 Java 场景------从文本中提取并验证电子邮件地址------进行代码实践,同时针对"零宽断言"和复杂模式的疑问进行详细说明。
1. 什么是正则表达式?
正则表达式(Regular Expression,简称 regex)是一种用于匹配、查找和操作文本的强大工具。它通过定义特定的模式(pattern),来描述需要匹配的字符串规则。
场景实践 :假设我们需要从一段文本中提取电子邮件地址,正则表达式可以帮助我们定义邮箱的模式,例如 [email protected]
。
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
结尾。例如:
- 输入:
[email protected] [email protected]
- 模式:
@(\w+)\.(\w+)(?=\.com$)
- 结果:只匹配
domain
,因为它后面是.com
,而xyz
后面是.org
,不满足条件。
为什么叫"零宽"? 因为它只检查条件,不消耗字符。比如在[email protected]
中,(?=\.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 = "联系方式: [email protected], [email protected], [email protected]";
// 简单模式:只用 \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+@
的局限性:- 只适合基本格式(如
[email protected]
)。 - 对于带
.
或-
的用户名(如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+
?对于
[email protected]
,\w+
确实够用。但真实邮箱可能包含.
或-
(如[email protected]
),简单模式会漏掉这些情况。复杂模式更灵活,能覆盖更多场景。
输出示例
java
简单模式匹配:
邮箱: [email protected], 用户名: alice123, 域名: domain.com
邮箱: [email protected], 用户名: alice, 域名: xyz.com
邮箱: [email protected], 用户名: bob, 域名: xyz.org
复杂模式匹配(仅限 .com):
邮箱: [email protected], 用户名: alice123, 域名: domain.com
总结
正则表达式是处理文本的利器,掌握其字符类、重复类、反义类、分组、零宽断言以及贪婪/懒惰模式,可以应对复杂的匹配需求。通过 Java 的 Pattern
和 Matcher
,我们可以轻松实现文本提取和验证功能。零宽断言为我们提供了位置检查的灵活性,而复杂模式则确保了更广泛的适用性。