Java 正则表达式学习指南:从入门到实践
正则表达式(Regular Expression,简称 Regex 或 Regexp)是一种强大的文本处理工具,它使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在文本搜索、替换、验证、解析等领域,正则表达式无处不在,是处理字符串的利器。
对于 Java 开发者而言,掌握正则表达式不仅能显著提高处理字符串的效率,更是解决许多实际问题的关键。Java 在 java.util.regex
包中提供了对正则表达式的强大支持,这使得在 Java 应用程序中应用正则表达式变得非常便捷。
本指南将带您深入了解 Java 正则表达式,从基础概念讲起,逐步深入到 Java 中如何使用这些概念,并通过丰富的代码示例帮助您巩固学习。
第一部分:正则表达式基础概念
在深入 Java 实现之前,我们首先需要理解正则表达式本身的核心构成和语法规则。正则表达式由一系列特殊字符和普通字符组成,这些字符组合起来形成一个模式,用于匹配目标字符串。
1. 普通字符 (Literal Characters)
大多数字符在正则表达式中都代表其本身,例如字母(a-z, A-Z)、数字(0-9)等。
示例:
* cat
会匹配字符串 “cat”。
* 123
会匹配字符串 “123”。
* Hello world
会匹配字符串 “Hello world”。
2. 元字符 (Metacharacters)
元字符是正则表达式中具有特殊含义的字符。它们不代表字符本身,而是用来描述字符的位置、类型或重复次数等。理解元字符是掌握正则表达式的关键。
常见的元字符包括:
* .
:匹配除换行符以外的任意单个字符。
* a.b
可以匹配 “aab”, “abb”, “a0b”, “a!b” 等。
* \
:转义符。用于将元字符转义成普通字符,或将普通字符转义成特殊序列。
* \.
匹配句号 .
.
* \\
匹配反斜杠 \
.
* \n
匹配换行符。
* \t
匹配制表符。
* ^
:匹配字符串的开头(或在多行模式下匹配行首)。
* ^abc
只匹配以 “abc” 开头的字符串。
* $
:匹配字符串的结尾(或在多行模式下匹配行尾)。
* abc$
只匹配以 “abc” 结尾的字符串。
* |
:逻辑或。匹配 |
符号左边或右边的表达式。
* cat|dog
匹配 “cat” 或 “dog”.
* ()
:分组。将多个字符或表达式视为一个整体。也可用于捕获匹配的子字符串(捕获组)。
* (ab)+
匹配 “ab”, “abab”, “ababab” 等。
* []
:字符集合。匹配方括号中列出的任意一个字符。
* [abc]
匹配 “a”, “b”, 或 “c”.
* {}
:限定符(Quantifiers)。指定前面的元素(字符、组或字符集合)出现的次数。
* a{3}
匹配 “aaa”.
* a{2,4}
匹配 “aa”, “aaa”, 或 “aaaa”.
* a{2,}
匹配 “aa” 或更多个 “a”.
* *
:匹配前面的元素零次或多次。等同于 {0,}
。
* a*
匹配 “”, “a”, “aa”, …
* +
:匹配前面的元素一次或多次。等同于 {1,}
。
* a+
匹配 “a”, “aa”, “aaa”, …
* ?
:匹配前面的元素零次或一次。等同于 {0,1}
。也用于非贪婪匹配。
* a?
匹配 “” 或 “a”.
3. 字符集合 (Character Classes)
字符集合 []
允许您匹配一类字符中的任意一个。
[abc]
:匹配 ‘a’, ‘b’, 或 ‘c’。[a-z]
:匹配任意小写字母。[A-Z]
:匹配任意大写字母。[0-9]
:匹配任意数字。[a-zA-Z]
:匹配任意字母。[a-zA-Z0-9]
:匹配任意字母或数字。[^...]
:否定字符集合。匹配不在列表中的任意字符。[^0-9]
匹配任意非数字字符。
除了自定义字符集合,还有一些预定义的字符类,它们是常用集合的简写:
\d
:匹配任意数字,等同于[0-9]
。\D
:匹配任意非数字字符,等同于[^0-9]
。\w
:匹配任意单词字符(字母、数字或下划线),等同于[a-zA-Z0-9_]
。\W
:匹配任意非单词字符,等同于[^a-zA-Z0-9_]
。\s
:匹配任意空白字符(空格、制表符、换行符等),等同于[ \t\n\r\f\v]
。\S
:匹配任意非空白字符,等同于[^ \t\n\r\f\v]
。
4. 限定符 (Quantifiers) 的贪婪、勉强与独占模式
限定符 (*
, +
, ?
, {n,m}
) 默认是“贪婪”的,它们会尽可能多地匹配字符。
- 贪婪 (Greedy): 默认模式。匹配最长的可能字符串。
- 模式:
<.*>
字符串:<a><b><c>
- 贪婪匹配结果:
<a><b><c>
(因为.*
尽可能多地匹配字符,直到最后一个>
前)
- 模式:
- 勉强 (Reluctant) / 非贪婪 (Lazy): 在限定符后加上
?
。匹配最短的可能字符串。- 模式:
<.*?>
字符串:<a><b><c>
- 勉强匹配结果:
<a>
,<b>
,<c>
(会找到多个最短匹配)
- 模式:
- 独占 (Possessive): 在限定符后加上
+
。类似于贪婪模式,但匹配后不再回溯。性能较高,但可能导致某些匹配失败。在 Java 中支持。- 模式:
<.*+>
字符串:<a><b><c>
- 独占匹配结果:无 (因为
.*+
会独占整个字符串,导致后面找不到>
)
- 模式:
在 Java 中,贪婪和勉强模式非常常用,独占模式用于特定性能优化场景。
5. 边界匹配器 (Boundary Matchers)
边界匹配器用于匹配特定的位置,而不是字符本身。
^
:匹配行的开头。$
:匹配行的结尾。\b
:匹配单词边界。一个单词边界是指一个\w
字符和一个\W
字符之间的位置,或者一个\w
字符与字符串开头/结尾之间的位置。\bcat\b
可以匹配 “cat” 在 “The cat sat” 中,但不能匹配 “catalogue” 中的 “cat”。
\B
:匹配非单词边界。
6. 分组与捕获 (Grouping and Capturing)
使用 ()
可以将一部分表达式组合成一个单元,这样就可以对其应用限定符,或者在匹配后提取这部分内容。
(pattern)
:创建捕获组。匹配的内容可以后续通过索引或名称引用。(?:pattern)
:创建非捕获组。只用于分组,不捕获匹配的内容。\n
:反向引用(Backreference)。匹配前面第 n 个捕获组匹配的内容。
示例:
* (\d{3})-(\d{3})-(\d{4})
匹配电话号码模式,并捕获区号、中间三位和最后四位。
* (ab)\1
匹配 “abab” (\1
引用了第一个组 (ab)
)。
* (?:ab)+
匹配 “ab”, “abab”, “ababab” 等,但不创建捕获组。
7. 零宽断言 (Lookarounds)
零宽断言是一种特殊的构造,它们匹配一个位置,而不是字符,并且它们断言(检查)在其左侧或右侧的字符是否存在某种模式,但这些字符本身不被包含在最终的匹配结果中。
(?=pattern)
:正向肯定预查 (Positive Lookahead)。匹配后面紧跟着pattern
的位置。Java(?=!)
匹配后面是 “!” 的 “Java” (只匹配 “Java”, 不包含 “!”)。在 “Java!” 中匹配 “Java”。
(?!pattern)
:正向否定预查 (Negative Lookahead)。匹配后面不跟着pattern
的位置。Java(?!!)
匹配后面不是 “!” 的 “Java”。在 “Java is cool” 中匹配 “Java”。
(?<=pattern)
:反向肯定预查 (Positive Lookbehind)。匹配前面是pattern
的位置。(?<=$)Java
匹配前面是 “$” 的 “Java” (只匹配 “Java”, 不包含 “$”)。在 “$Java” 中匹配 “Java”。
(?<!pattern)
:反向否定预查 (Negative Lookbehind)。匹配前面不是pattern
的位置。(?<!$)Java
匹配前面不是 “$” 的 “Java”。在 “The Java language” 中匹配 “Java”。
注意:Java 的反向断言 ((?<=...)
和 (?<!...)
) 要求内部的模式 pattern
必须能确定长度(定长)。某些 Java 版本或特定情况下可能支持非定长,但通常建议使用定长模式以确保兼容性和可预测性。
第二部分:Java 中的正则表达式
Java 在 java.util.regex
包中提供了 Pattern
和 Matcher
两个核心类来支持正则表达式操作。
Pattern
:表示一个编译好的正则表达式。它是一个不可变(immutable)的对象,线程安全。Matcher
:是一个状态机,用于对输入字符串执行模式匹配操作。它是通过Pattern
对象创建的,非线程安全。
1. Pattern 类的使用
使用 Pattern
类的第一步是编译正则表达式字符串。编译是提高性能的关键,特别是当您需要重复使用同一个正则表达式进行多次匹配时。
“`java
import java.util.regex.Pattern;
// 编译正则表达式
String regex = “abc”;
Pattern pattern = Pattern.compile(regex);
// 如果正则表达式语法错误,compile 方法会抛出 PatternSyntaxException
// Pattern pattern = Pattern.compile(“[a-z”); // 这会抛异常
“`
Pattern.compile()
方法接受一个字符串参数,即正则表达式。它还有一个重载方法,可以接受一个标志参数,用于修改匹配行为(如忽略大小写、多行模式等)。
常用的标志(Flags):
Pattern.CASE_INSENSITIVE
(或i
): 忽略大小写匹配。Pattern.MULTILINE
(或m
):^
和$
匹配行的开头和结尾,而不仅仅是整个字符串的开头和结尾。Pattern.DOTALL
(或s
):.
匹配包括换行符在内的任意字符。Pattern.COMMENTS
(或x
): 忽略模式中的空白和注释(#
开头的行)。
“`java
// 编译一个忽略大小写的模式
Pattern patternCaseInsensitive = Pattern.compile(“java”, Pattern.CASE_INSENSITIVE);
// 编译一个多行模式
Pattern patternMultiline = Pattern.compile(“^line”, Pattern.MULTILINE);
“`
Pattern
类还有一个静态方法 Pattern.matches(String regex, CharSequence input)
,它可以方便地一次性完成编译和匹配整个字符串的操作,返回布尔值。这适用于一次性的简单匹配,但不适用于多次重复匹配或查找子串,因为每次调用都会重新编译模式。
java
// 简单判断整个字符串是否匹配某个模式
boolean isMatch = Pattern.matches("a.c", "abc"); // true
boolean isMatch2 = Pattern.matches("a.c", "abbc"); // false (整个字符串不匹配)
2. Matcher 类的使用
一旦有了 Pattern
对象,就可以使用它的 matcher()
方法创建一个 Matcher
对象,用于对特定的输入字符串进行匹配操作。
“`java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
String regex = “\d+”; // 匹配一个或多个数字
String input = “Hello 123 World 456”;
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input); // 创建 Matcher 对象
“`
Matcher
类提供了多种方法来执行不同类型的匹配操作:
-
boolean matches()
:尝试将整个输入序列与模式进行匹配。如果整个输入匹配成功,则返回true
,否则返回false
。
“`java
Pattern p = Pattern.compile(“\d+”);
Matcher m = p.matcher(“123”);
System.out.println(m.matches()); // truem = p.matcher(“123abc”);
System.out.println(m.matches()); // false (因为abc没有匹配上)
“` -
boolean find()
:尝试查找与模式匹配的输入序列的下一个子序列。如果找到,则返回true
,并更新 Matcher 的状态以描述匹配到的子序列。通常在循环中使用,查找所有匹配项。
“`java
String input = “數字: 123, 另一個數字: 456.”;
Pattern pattern = Pattern.compile(“\d+”);
Matcher matcher = pattern.matcher(input);while (matcher.find()) {
// matcher.group() 返回当前匹配到的子字符串
System.out.println(“找到匹配: ” + matcher.group());
// matcher.start() 返回当前匹配的起始索引
System.out.println(“起始位置: ” + matcher.start());
// matcher.end() 返回当前匹配的结束索引+1
System.out.println(“结束位置: ” + matcher.end());
}
// 输出:
// 找到匹配: 123
// 起始位置: 3
// 结束位置: 6
// 找到匹配: 456
// 起始位置: 18
// 结束位置: 21
“` -
boolean lookingAt()
:尝试将输入序列从头开始与模式进行匹配。与matches()
不同,它不需要整个输入序列都匹配。如果输入序列的开头部分与模式匹配,则返回true
。
“`java
Pattern p = Pattern.compile(“abc”);
Matcher m = p.matcher(“abcdef”);
System.out.println(m.lookingAt()); // truem = p.matcher(“defabc”);
System.out.println(m.lookingAt()); // false (开头不匹配)
“` -
String group()
:返回由之前成功的匹配操作找到的子序列。 String group(int group)
:返回由之前成功的匹配操作找到的指定捕获组的子序列。组索引从 1 开始,组 0 代表整个匹配。int groupCount()
:返回模式中的捕获组数量(不包括组 0)。
“`java
String input = “电话号码: (123) 456-7890”;
String regex = “\((\d{3})\) (\d{3})-(\d{4})”; // 三个捕获组
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
System.out.println(“整个匹配: ” + matcher.group(0)); // 或 matcher.group()
System.out.println(“区号 (组 1): ” + matcher.group(1));
System.out.println(“前三位 (组 2): ” + matcher.group(2));
System.out.println(“后四位 (组 3): ” + matcher.group(3));
System.out.println(“总捕获组数: ” + matcher.groupCount()); // 3
}
// 输出:
// 整个匹配: (123) 456-7890
// 区号 (组 1): 123
// 前三位 (组 2): 456
// 后四位 (组 3): 7890
// 总捕获组数: 3
“`
3. 替换操作
Matcher
类提供了强大的替换功能:
String replaceAll(String replacement)
:替换所有与模式匹配的子序列。replacement
字符串可以包含对捕获组的反向引用(如$1
,$2
等)。String replaceFirst(String replacement)
:替换第一个与模式匹配的子序列。
“`java
String input = “Email: [email protected], Another: [email protected]”;
String regex = “\w+@\w+\.\w+”; // 匹配一个简单的邮箱格式
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
// 替换所有邮箱为 [REDACTED]
String outputAll = matcher.replaceAll(“[REDACTED]”);
System.out.println(“替换所有: ” + outputAll);
// 输出: 替换所有: Email: [REDACTED], Another: [REDACTED]
// 替换第一个邮箱为 [HIDDEN]
// 注意:Matcher对象是非线程安全的,且一次find/replace操作后状态改变,
// 如果要重新从头开始匹配,需要重置或创建新的Matcher
matcher.reset(); // 重置Matcher到输入字符串的开头
String outputFirst = matcher.replaceFirst(“[HIDDEN]”);
System.out.println(“替换第一个: ” + outputFirst);
// 输出: 替换第一个: Email: [HIDDEN], Another: [email protected]
“`
在替换字符串中使用反向引用:
“`java
String input = “Lastname, Firstname”;
String regex = “(\w+), (\w+)”; // 捕获姓和名
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern(input);
// 将 “Lastname, Firstname” 格式转换为 “Firstname Lastname”
String output = matcher.replaceAll(“$2 $1”); // $2 引用第二个捕获组 (名),$1 引用第一个捕获组 (姓)
System.out.println(“格式转换: ” + output);
// 输出: 格式转换: Firstname Lastname
“`
4. 分割字符串
Pattern
类提供了一个方便的 split()
方法,可以根据正则表达式作为分隔符来分割字符串。
“`java
String input = “apple,banana;orange:grape”;
String regex = “[,;:]”; // 匹配逗号、分号或冒号作为分隔符
Pattern pattern = Pattern.compile(regex);
String[] fruits = pattern.split(input);
for (String fruit : fruits) {
System.out.println(fruit);
}
// 输出:
// apple
// banana
// orange
// grape
// Pattern.split() 也有一个方便的快捷方式在 String 类中
String[] fruits2 = input.split(“[,;:]”); // 等同于上面的 Pattern.split()
“`
5. String 类中的正则表达式方法
为了方便,String
类本身也提供了一些内置的使用正则表达式的方法:
boolean matches(String regex)
:判断整个字符串是否匹配给定的正则表达式。内部调用Pattern.matches()
。String replaceAll(String regex, String replacement)
:使用正则表达式替换所有匹配项。内部创建 Pattern 和 Matcher。String replaceFirst(String regex, String replacement)
:使用正则表达式替换第一个匹配项。内部创建 Pattern and Matcher。String[] split(String regex)
:使用正则表达式分割字符串。内部创建 Pattern。
这些方法适用于一次性的操作,如果您需要在循环中多次使用同一个正则表达式,或者需要更复杂的匹配(如查找所有子串、获取捕获组等),则应该显式使用 Pattern
和 Matcher
类以获得更好的性能和灵活性。
第三部分:实践与常见用例
掌握了基础概念和 Java API 后,让我们看一些实际的应用场景和代码示例。
1. 校验电子邮件地址
这是一个非常常见的用例。需要注意的是,一个完全符合 RFC 标准的电子邮件正则表达式会非常复杂。下面的示例是一个简化版本,适用于大多数常见格式。
“`java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EmailValidator {
// 简化的邮箱验证正则
private static final String EMAIL_REGEX =
“^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$”;
private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
public static boolean isValidEmail(String email) {
if (email == null) {
return false;
}
Matcher matcher = EMAIL_PATTERN.matcher(email);
return matcher.matches(); // 使用 matches() 确保整个字符串都匹配
}
public static void main(String[] args) {
System.out.println("[email protected] is valid: " + isValidEmail("[email protected]")); // true
System.out.println("[email protected] is valid: " + isValidEmail("[email protected]")); // true
System.out.println("test@example is valid: " + isValidEmail("test@example")); // false
System.out.println("[email protected] is valid: " + isValidEmail("[email protected]")); // false
System.out.println("@example.com is valid: " + isValidEmail("@example.com")); // false
}
}
“`
解释一下上面的正则表达式 ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$
:
^
: 匹配字符串开头。[a-zA-Z0-9._%+-]+
: 匹配用户名部分。包含字母、数字、点号.
、下划线_
、百分号%
、加号+
、减号-
。+
表示至少出现一次。@
: 匹配字面字符@
。[a-zA-Z0-9.-]+
: 匹配域名部分。包含字母、数字、点号.
、减号-
。+
表示至少出现一次。\\.
: 匹配字面字符.
. 需要转义因为.
是元字符。[a-zA-Z]{2,6}
: 匹配顶级域名(如 com, org, co.uk 等的最后一部分)。包含字母,长度在 2 到 6 之间。$
: 匹配字符串结尾。
2. 提取 HTML 标签内容 (简易版)
提取文本中的特定模式。
“`java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HtmlContentExtractor {
public static void main(String[] args) {
String html = “
This is a paragraph.
Another one.
“;
// 匹配
…
标签及其内容
// 注意这里使用了非贪婪匹配 (.?) 以避免匹配到最后一个
String regex = “
(.?)
“;
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(html);
System.out.println("提取所有 <p> 标签内容:");
while (matcher.find()) {
// 组 1 捕获了标签内部的内容
System.out.println(" - " + matcher.group(1));
}
// 输出:
// 提取所有 <p> 标签内容:
// - This is a **paragraph**.
// - Another one.
}
}
“`
注意:使用正则表达式解析 HTML/XML 通常不是推荐的做法,因为它们的结构可能很复杂且嵌套深,用专门的解析器(如 Jsoup)更健壮。这个例子仅用于演示 find()
和 group()
的用法。
3. 查找并替换特定模式
“`java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TextReplacer {
public static void main(String[] args) {
String text = “订单号:ORD12345。另一订单:ORD67890。”;
String regex = “ORD(\d+)”; // 匹配 “ORD” 后跟一个或多个数字,捕获数字部分
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
// 将订单号替换为 "[订单号 $1]",$1 引用捕获的数字
String replacedText = matcher.replaceAll("[订单号 $1]");
System.out.println("原始文本: " + text);
System.out.println("替换后文本: " + replacedText);
// 输出:
// 原始文本: 订单号:ORD12345。另一订单:ORD67890。
// 替换后文本: 订单号:[订单号 12345]。另一订单:[订单号 67890]。
}
}
“`
4. 分割字符串
“`java
import java.util.regex.Pattern;
public class SplitExample {
public static void main(String[] args) {
String data = “ID:101 Name:Alice Age:30”;
// 使用空格或冒号作为分隔符
String regex = “[ :]”;
Pattern pattern = Pattern.compile(regex);
String[] parts = pattern.split(data);
System.out.println("分割结果:");
for (String part : parts) {
if (!part.isEmpty()) { // split可能会产生空字符串,过滤掉
System.out.println("- " + part);
}
}
// 输出:
// 分割结果:
// - ID
// - 101
// - Name
// - Alice
// - Age
// - 30
}
}
“`
第四部分:Java 正则表达式进阶与注意事项
1. 性能考虑
- 编译 Pattern: 如果同一个正则表达式需要被多次使用(例如,在循环中处理多行文本),一定要先编译
Pattern
对象,然后在循环中重用它来创建Matcher
对象。不要在每次匹配时都调用Pattern.compile()
或使用String.matches()
等快捷方法,这会显著降低性能。 - Catastrophic Backtracking (灾难性回溯): 这是正则表达式中一个严重的性能问题。某些模式和输入字符串组合会导致正则表达式引擎在尝试匹配时陷入指数级增长的回溯过程,从而导致程序挂起或运行极慢。典型的模式是嵌套的量词,如
(a+)+
或(.+)*
。当输入字符串无法完全匹配但前缀有很多符合模式的部分时,引擎会反复尝试回溯不同的匹配组合。- 例如,模式
(a+)+b
匹配字符串 “aaaaaaaaaaaaX”。引擎会不断尝试匹配不同数量的a
,再尝试匹配b
,失败后回溯,再尝试不同数量的a
… 导致性能急剧下降。 - 避免方法:简化模式,使用非捕获组
(?:...)
,使用独占量词*+
,++
,?+
,{n,m}+
(如果逻辑允许且你知道不会回溯成功的地方)。对于上面的例子,如果确定只想匹配连续的a
,可以使用a+b
或a++b
。
- 例如,模式
- 使用非捕获组
(?:...)
: 如果分组仅用于应用限定符或进行逻辑组合,而不需要后续引用匹配内容,使用非捕获组可以稍微提高性能并使捕获组的索引更清晰。
2. 转义的陷阱
Java 字符串本身使用 \
作为转义符。而正则表达式也使用 \
作为转义符。这意味着,如果您想在正则表达式中表示一个字面反斜杠 \
或其他需要转义的元字符,您需要在 Java 字符串层面先进行一次转义。
例如:
* 要在正则表达式中匹配字面点号 .
,需要写成 \.
。在 Java 字符串中表示它,需要写成 "\\."
。
* 要在正则表达式中匹配字面反斜杠 \
,需要写成 \\
。在 Java 字符串中表示它,需要写成 "\\\\"
。
* 匹配 Windows 文件路径 C:\Program Files\
,正则表达式可能是 C:\\Program Files\\
,在 Java 字符串中需要写成 "C:\\\\Program Files\\\\"
。
3. Matcher
状态与重置
Matcher
对象在执行 find()
, matches()
, lookingAt()
, replaceAll()
, replaceFirst()
等操作后会更新其内部状态(如当前位置、匹配到的子串信息)。如果想在同一个 Matcher
对象上对 同一个输入字符串 重新从头开始进行匹配,需要调用 matcher.reset()
方法。如果您要匹配 不同的输入字符串,需要使用 pattern.matcher(newInputString)
创建一个新的 Matcher
对象。
4. 多行模式 (MULTILINE
) 和点号模式 (DOTALL
)
理解 Pattern.MULTILINE
和 Pattern.DOTALL
标志非常重要,因为它们会改变 ^
, $
, 和 .
元字符的默认行为。
MULTILINE
:- 默认情况下,
^
匹配整个输入字符串的开头,$
匹配整个输入字符串的结尾(或在最后一个换行符之前)。 - 启用
MULTILINE
后,^
也匹配换行符\n
或回车符\r
之后 的位置,$
也匹配换行符\n
或回车符\r
之前 的位置。
- 默认情况下,
DOTALL
:- 默认情况下,
.
匹配除换行符\n
以外的任意字符。 - 启用
DOTALL
后,.
可以匹配包括换行符在内的任意字符。这使得. *
可以真正匹配跨越多行的任意内容。
- 默认情况下,
5. 内嵌标志
除了在 Pattern.compile()
方法中设置标志,还可以在正则表达式字符串的开头使用 (?flags)
的形式设置内嵌标志,或者在模式中局部开启/关闭标志 (?flags:pattern)
。
(?i)
: 开启忽略大小写。(?m)
: 开启多行模式。(?s)
: 开启 DOTALL 模式。(?x)
: 开启注释模式。- 例如:
(?i)java
匹配 “java”, “Java”, “JAVA” 等。 Hello (?i:w)orld
: 只对w
忽略大小写。Hello (?i)w(?-i)orld
: 开启忽略大小写匹配w
,然后关闭忽略大小写匹配orld
。
内嵌标志可以在模式中灵活控制匹配行为,有时比在 compile
方法中设置全局标志更方便。
第五部分:学习资源与工具
- 官方 Java 文档:
java.util.regex
包的 Javadoc 是最权威的参考资料,详细列出了所有类和方法的说明。 - 在线正则表达式测试工具: 许多网站提供在线测试器,您可以输入正则表达式和测试字符串,实时查看匹配结果、捕获组等。这对于调试和学习非常有帮助(例如: regex101.com, regexr.com)。
- 正则表达式教程网站/书籍: 有很多优秀的在线教程和书籍系统地讲解正则表达式语法。
结论
正则表达式是字符串处理领域的瑞士军刀,它提供了一种简洁而强大的方式来描述和操作文本模式。Java 通过 java.util.regex
包提供了全面的正则表达式支持,其中 Pattern
用于编译模式,Matcher
用于执行匹配操作。
掌握正则表达式需要时间和实践。从理解基本的元字符和限定符开始,逐步学习字符集合、分组、边界匹配和零宽断言。在 Java 中,熟悉 Pattern.compile()
, Matcher.find()
, matcher.matches()
, matcher.group()
, matcher.replaceAll()
, pattern.split()
等核心方法,并理解它们的用法和性能差异。
通过不断的练习和查阅文档,您将能够自信地使用正则表达式来解决各种复杂的文本处理任务,从而提高您的开发效率和程序健壮性。祝您在正则表达式的学习旅程中取得成功!