正则表达式(Regular Expression)是一种用于描述字符串模式的强大工具,广泛应用于字符串匹配、查找、替换、验证等场景。无论是Java开发中的数据校验、日志解析,还是SQL中的模糊查询,亦或是日常的文本处理,掌握正则表达式都能大幅提升效率。本文将从底层逻辑出发,由浅入深、系统全面地讲解正则表达式,结合JDK 17环境下的可运行实例,帮你彻底吃透这门技术。
一、为什么要学正则表达式?底层价值与应用场景
在讲解具体语法之前,我们先搞清楚:正则表达式的核心价值是什么?为什么不直接用字符串的indexOf、contains等方法?
1. 底层价值:用“模式”匹配无限字符串
字符串的基础方法(如contains)只能匹配固定的子串,而正则表达式通过定义“字符模式”,可以匹配一类符合规则的字符串。例如:
匹配所有手机号(11位数字,以13/14/15/17/18/19开头);
匹配所有邮箱(包含@,@前为用户名,@后为域名);
匹配所有日期格式(如yyyy-MM-dd、yyyy/MM/dd)。
这种“模式化匹配”的能力,是正则表达式的核心,也是其能够应对复杂字符串处理场景的根本原因。
2. 核心应用场景
正则表达式的应用遍布软件开发的各个环节,典型场景包括:
数据验证:用户输入的手机号、邮箱、身份证号、密码强度校验;
文本处理:日志解析(提取日志中的时间、错误码、用户ID)、文本内容替换(批量替换指定格式的字符串);
数据提取:从HTML/XML文本中提取指定标签内容、从JSON字符串中提取特定字段;
数据库查询:MySQL中的
REGEXP运算符,实现复杂的模糊查询;配置解析:解析配置文件中的特定格式配置项(如.properties文件中的键值对)。
3. 正则表达式的执行流程(底层逻辑)
正则表达式的执行本质上是“模式匹配引擎”对输入字符串的扫描与匹配过程。不同语言的正则引擎实现略有差异,但核心流程一致:
编译正则表达式:将正则表达式字符串转换为高效的匹配引擎(有限自动机);
输入字符串扫描:匹配引擎按顺序扫描输入字符串的每个字符;
模式匹配校验:判断当前扫描位置的字符是否符合正则表达式定义的模式;
匹配结果返回:若匹配成功,返回匹配到的子串、位置等信息;若失败,返回无匹配。
流程图如下:
二、正则表达式基础语法:吃透核心元字符
正则表达式的语法核心是“元字符”——具有特殊含义的字符。掌握元字符的含义和用法,是学习正则表达式的基础。我们将元字符分为“基础匹配元字符”“量词元字符”“边界匹配元字符”“分组与引用元字符”四类,逐一讲解。
1. 基础匹配元字符(匹配单个字符)
基础元字符用于匹配单个字符,是构成正则表达式的最小单位。
| 元字符 | 含义 | 示例 | 匹配结果 |
|---|---|---|---|
| . | 匹配任意单个字符(除换行符\n) | a.b | aab、acb、a1b(不匹配a\nb) |
| [] | 匹配括号内的任意一个字符 | [abc] | a、b、c |
| [^] | 匹配不在括号内的任意一个字符 | [^abc] | d、1、@(不匹配a、b、c) |
| \d | 匹配数字字符(0-9) | \d{3} | 123、456、789 |
| \D | 匹配非数字字符 | \D{2} | ab、@#、A1(不匹配12、34) |
| \w | 匹配单词字符(a-z、A-Z、0-9、_) | \w+ | hello、Hello123、user_name |
| \W | 匹配非单词字符 | \W{2} | @#、$%、&* |
| \s | 匹配空白字符(空格、制表符\t、换行符\n、回车符\r) | \s+ | 空格、\t、\n\r |
| \S | 匹配非空白字符 | \S{3} | abc、123、@#$ |
关键说明:
元字符
.不匹配换行符\n,若需匹配包括换行符在内的任意字符,在Java中需使用Pattern.DOTALL标志;方括号
[]内的元字符会失去特殊含义,例如[.]匹配的是字符.,而非任意字符;方括号
[]内可以使用-表示范围,例如[a-z]匹配小写字母,[0-9a-zA-Z]匹配字母和数字,[1-35-7]匹配1-3或5-7的数字。
Java实例:基础元字符匹配
package com.jam.demo.regex; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 基础元字符匹配示例 * @author ken */ @Slf4j public class BasicMetaCharDemo { public static void main(String[] args) { // 1. 测试元字符 . (匹配任意单个字符,除\n) String regex1 = "a.b"; String str1 = "aab\nacb\na1b\na\nb"; matchAndLog(regex1, str1, "元字符 . 匹配"); // 2. 测试元字符 [] (匹配括号内任意字符) String regex2 = "[abc]"; String str2 = "a1b2c3d4"; matchAndLog(regex2, str2, "元字符 [] 匹配"); // 3. 测试元字符 [^] (匹配不在括号内的字符) String regex3 = "[^abc]"; String str3 = "a1b2c3d4"; matchAndLog(regex3, str3, "元字符 [^] 匹配"); // 4. 测试元字符 \d (匹配数字) String regex4 = "\\d{3}"; // 后续讲解量词,这里表示匹配3个连续数字 String str4 = "abc123def456ghi78"; matchAndLog(regex4, str4, "元字符 \\d 匹配"); // 5. 测试元字符 \s (匹配空白字符) String regex5 = "\\s+"; String str5 = "hello world\tjava\nregex\rtest"; matchAndLog(regex5, str5, "元字符 \\s 匹配"); } /** * 执行正则匹配并打印结果 * @param regex 正则表达式 * @param input 输入字符串 * @param testName 测试名称 */ private static void matchAndLog(String regex, String input, String testName) { if (StringUtils.isEmpty(regex) || StringUtils.isEmpty(input)) { log.error("{}:正则表达式或输入字符串不能为空", testName); return; } // 编译正则表达式(推荐复用Pattern,提升性能) Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(input); log.info("===== {} =====", testName); log.info("正则表达式:{}", regex); log.info("输入字符串:{}", input); log.info("匹配结果:"); while (matcher.find()) { // 打印匹配到的子串及其起始、结束位置 log.info("匹配到:'{}',起始位置:{},结束位置:{}", matcher.group(), matcher.start(), matcher.end()); } log.info("===== {} 结束 =====\n", testName); } }运行结果(关键部分):
===== 元字符 . 匹配 ===== 正则表达式:a.b 输入字符串:aab acb a1b a b 匹配结果: 匹配到:'aab',起始位置:0,结束位置:3 匹配到:'acb',起始位置:4,结束位置:7 匹配到:'a1b',起始位置:8,结束位置:11 ===== 元字符 . 匹配 结束 ===== ===== 元字符 [] 匹配 ===== 正则表达式:[abc] 输入字符串:a1b2c3d4 匹配结果: 匹配到:'a',起始位置:0,结束位置:1 匹配到:'b',起始位置:2,结束位置:3 匹配到:'c',起始位置:4,结束位置:5 ===== 元字符 [] 匹配 结束 =====2. 量词元字符(匹配多个连续字符)
基础元字符只能匹配单个字符,量词元字符用于指定“前面的元素(单个字符或分组)需要匹配的次数”,是实现“模式匹配”的核心。
| 元字符 | 含义 | 示例 | 匹配结果 |
|---|---|---|---|
| * | 匹配前面的元素0次或多次(贪婪匹配) | ab* | a、ab、abb、abbb |
| + | 匹配前面的元素1次或多次(贪婪匹配) | ab+ | ab、abb、abbb(不匹配a) |
| ? | 匹配前面的元素0次或1次(贪婪匹配) | ab? | a、ab(不匹配abb) |
| {n} | 匹配前面的元素恰好n次 | ab{3} | abbb(恰好3个b) |
| {n,} | 匹配前面的元素至少n次(贪婪匹配) | ab{2,} | abb、abbb、abbbb |
| {n,m} | 匹配前面的元素至少n次、至多m次(贪婪) | ab{2,4} | abb、abbb、abbbb(不超过4个b) |
| *? | 匹配前面的元素0次或多次(非贪婪匹配) | ab*? | a、ab(优先匹配最少次数) |
| +? | 匹配前面的元素1次或多次(非贪婪匹配) | ab+? | ab(优先匹配最少次数) |
| ?? | 匹配前面的元素0次或1次(非贪婪匹配) | ab?? | a(优先匹配0次) |
| {n,m}? | 匹配前面的元素n到m次(非贪婪) | ab{2,4}? | abb(优先匹配最少的2次) |
关键说明:
贪婪匹配:默认模式,尽可能匹配最多的字符(例如
ab*匹配abbb时,会匹配整个abbb,而非a或ab);非贪婪匹配:在量词后加
?,尽可能匹配最少的字符(例如ab*?匹配abbb时,会优先匹配a,而非abbb);量词作用于“前面紧邻的单个元素”,若需作用于多个元素,需使用分组(后续讲解)。
贪婪 vs 非贪婪:核心差异实例
package com.jam.demo.regex; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 贪婪匹配与非贪婪匹配对比示例 * @author ken */ @Slf4j public class GreedyVsLazyDemo { public static void main(String[] args) { String input = "aabbaaccbb"; // 1. 贪婪匹配:ab+ 尽可能匹配最多的b String greedyRegex = "ab+"; matchAndLog(greedyRegex, input, "贪婪匹配 ab+"); // 2. 非贪婪匹配:ab+? 尽可能匹配最少的b(1个) String lazyRegex = "ab+?"; matchAndLog(lazyRegex, input, "非贪婪匹配 ab+?"); // 3. 贪婪匹配:a.*b 匹配从第一个a到最后一个b的所有字符 String greedyRegex2 = "a.*b"; matchAndLog(greedyRegex2, input, "贪婪匹配 a.*b"); // 4. 非贪婪匹配:a.*?b 匹配从第一个a到最近的b的字符 String lazyRegex2 = "a.*?b"; matchAndLog(lazyRegex2, input, "非贪婪匹配 a.*?b"); } /** * 执行正则匹配并打印结果(复用方法) * @param regex 正则表达式 * @param input 输入字符串 * @param testName 测试名称 */ private static void matchAndLog(String regex, String input, String testName) { if (StringUtils.isEmpty(regex) || StringUtils.isEmpty(input)) { log.error("{}:正则表达式或输入字符串不能为空", testName); return; } Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(input); log.info("===== {} =====", testName); log.info("正则表达式:{}", regex); log.info("输入字符串:{}", input); log.info("匹配结果:"); while (matcher.find()) { log.info("匹配到:'{}',起始位置:{},结束位置:{}", matcher.group(), matcher.start(), matcher.end()); } log.info("===== {} 结束 =====\n", testName); } }运行结果(核心差异):
===== 贪婪匹配 ab+ ===== 正则表达式:ab+ 输入字符串:aabbaaccbb 匹配结果: 匹配到:'abb',起始位置:1,结束位置:4 // 匹配到1个a后面的2个b(最多) ===== 贪婪匹配 ab+ 结束 ===== ===== 非贪婪匹配 ab+? ===== 正则表达式:ab+? 输入字符串:aabbaaccbb 匹配结果: 匹配到:'ab',起始位置:1,结束位置:3 // 匹配到1个a后面的1个b(最少) ===== 非贪婪匹配 ab+? 结束 ===== ===== 贪婪匹配 a.*b ===== 正则表达式:a.*b 输入字符串:aabbaaccbb 匹配结果: 匹配到:'aabbaaccbb',起始位置:0,结束位置:10 // 从第一个a到最后一个b ===== 贪婪匹配 a.*b 结束 ===== ===== 非贪婪匹配 a.*?b ===== 正则表达式:a.*?b 输入字符串:aabbaaccbb 匹配结果: 匹配到:'aab',起始位置:0,结束位置:3 // 从第一个a到最近的b ===== 非贪婪匹配 a.*?b 结束 =====3. 边界匹配元字符(匹配字符串的边界位置)
边界匹配元字符不匹配具体字符,而是匹配“字符串的边界位置”(如字符串开头、结尾、单词边界),常用于精准匹配(避免部分匹配)。
| 元字符 | 含义 | 示例 | 匹配结果 |
|---|---|---|---|
| ^ | 匹配字符串的开头(多行模式下匹配行开头) | ^abc | abc(字符串以abc开头,不匹配xabc) |
| $ | 匹配字符串的结尾(多行模式下匹配行结尾) | abc$ | abc(字符串以abc结尾,不匹配abcx) |
| \b | 匹配单词边界(单词字符与非单词字符之间) | \bhello\b | hello(单独的hello单词,不匹配helloworld) |
| \B | 匹配非单词边界 | \Bhello\B | hello(在单词内部,如helloworld中的hello) |
关键说明:
^和$在默认模式下(单行模式)匹配整个字符串的开头和结尾;在多行模式(Pattern.MULTILINE)下,^匹配每一行的开头,$匹配每一行的结尾;\b的“单词边界”是指:一侧是单词字符(\w),另一侧是非单词字符(\W)或字符串边界(开头/结尾)。
边界匹配实例(精准验证场景)
package com.jam.demo.regex; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 边界匹配元字符示例(精准验证场景) * @author ken */ @Slf4j public class BoundaryMatchDemo { public static void main(String[] args) { // 1. 验证字符串是否为纯数字(精准匹配,整个字符串都是数字) String numberRegex = "^\\d+$"; String[] numberTests = {"12345", "123a45", " 12345 ", "0"}; for (String test : numberTests) { boolean isMatch = Pattern.matches(numberRegex, test); log.info("字符串'{}'是否为纯数字:{}", test, isMatch); } // 2. 验证字符串是否以abc开头(单行模式 vs 多行模式) String startWithAbc = "^abc"; String multiLineInput = "abc123\nabc456\nxabc789"; // 单行模式(默认):只匹配整个字符串的开头 Matcher singleLineMatcher = Pattern.compile(startWithAbc).matcher(multiLineInput); // 多行模式:匹配每一行的开头 Matcher multiLineMatcher = Pattern.compile(startWithAbc, Pattern.MULTILINE).matcher(multiLineInput); log.info("\n===== 单行模式下匹配 ^abc ====="); while (singleLineMatcher.find()) { log.info("匹配到:'{}',位置:{}~{}", singleLineMatcher.group(), singleLineMatcher.start(), singleLineMatcher.end()); } log.info("\n===== 多行模式下匹配 ^abc ====="); while (multiLineMatcher.find()) { log.info("匹配到:'{}',位置:{}~{}", multiLineMatcher.group(), multiLineMatcher.start(), multiLineMatcher.end()); } // 3. 匹配单独的hello单词(不匹配helloworld或hello123) String wordRegex = "\\bhello\\b"; String wordInput = "hello helloworld hello123 hello@world @hello@"; Matcher wordMatcher = Pattern.compile(wordRegex).matcher(wordInput); log.info("\n===== 匹配单独的hello单词 ====="); while (wordMatcher.find()) { log.info("匹配到:'{}',位置:{}~{}", wordMatcher.group(), wordMatcher.start(), wordMatcher.end()); } } }运行结果:
字符串'12345'是否为纯数字:true 字符串'123a45'是否为纯数字:false 字符串' 12345 '是否为纯数字:false 字符串'0'是否为纯数字:true ===== 单行模式下匹配 ^abc ===== 匹配到:'abc',位置:0~3 ===== 多行模式下匹配 ^abc ===== 匹配到:'abc',位置:0~3 匹配到:'abc',位置:7~10 ===== 匹配单独的hello单词 ===== 匹配到:'hello',位置:0~5 匹配到:'hello',位置:24~294. 分组与引用元字符(匹配多个字符的组合)
当需要将多个字符作为一个整体(组合)进行匹配时,需要使用“分组”元字符。分组还支持“引用”(重复匹配已匹配的分组内容)和“命名分组”(更清晰地获取分组结果)。
| 元字符 | 含义 | 示例 | 匹配结果 |
|---|---|---|---|
| (pattern) | 捕获组:将pattern作为一个分组,可引用 | (ab)+ | ab、abab、ababab |
| \n | 引用第n个捕获组的内容(n为正整数) | (ab)c\1 | abcab(\1引用第一个分组的ab) |
| (?:pattern) | 非捕获组:只分组,不捕获(无法引用) | (?:ab)+ | ab、abab(无法用\1引用) |
| (?p) | 命名捕获组:给分组命名为name | (?ab)c\k | abcab(\k引用命名分组ab) |
| (?=pattern) | 正向预查:匹配后面紧跟pattern的位置 | abc(?=123) | abc(后面紧跟123,不匹配abc456) |
| (?!pattern) | 负向预查:匹配后面不紧跟pattern的位置 | abc(?!123) | abc(后面不紧跟123,匹配abc456) |
| (?<=pattern) | 正向后查:匹配前面紧跟pattern的位置 | (?<=123)abc | abc(前面紧跟123,不匹配456abc) |
| (?<!pattern) | 负向后查:匹配前面不紧跟pattern的位置 | (?<!123)abc | abc(前面不紧跟123,匹配456abc) |
关键说明:
捕获组:会将匹配到的分组内容保存到内存中,可通过
Matcher.group(n)获取(n从1开始,0表示整个匹配结果);非捕获组:仅用于将多个字符视为一个整体,不保存分组内容,性能优于捕获组(无需内存存储);
预查(零宽断言):只匹配“位置”,不匹配具体字符(匹配结果长度为0),用于限定匹配的上下文环境;
命名分组在Java 7及以上支持,通过
Matcher.group("name")获取分组内容,比数字引用更易读。
分组与引用实例
package com.jam.demo.regex; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 分组与引用元字符示例 * @author ken */ @Slf4j public class GroupReferenceDemo { public static void main(String[] args) { // 1. 捕获组:匹配连续重复的ab(ab、abab、ababab) String captureGroupRegex = "(ab)+"; String captureInput = "ab abab ababab abc"; Matcher captureMatcher = Pattern.compile(captureGroupRegex).matcher(captureInput); log.info("===== 捕获组 (ab)+ 匹配 ====="); while (captureMatcher.find()) { log.info("完整匹配:'{}',第1个分组(ab):'{}'", captureMatcher.group(0), captureMatcher.group(1)); } // 2. 引用分组:匹配ab cab(第一个分组ab,后面重复ab) String referenceRegex = "(ab)c\\1"; String referenceInput = "abcab abcxab abcabab"; Matcher referenceMatcher = Pattern.compile(referenceRegex).matcher(referenceInput); log.info("\n===== 引用分组 (ab)c\\1 匹配 ====="); while (referenceMatcher.find()) { log.info("完整匹配:'{}',引用的分组内容:'{}'", referenceMatcher.group(0), referenceMatcher.group(1)); } // 3. 命名分组:匹配日期(yyyy-MM-dd),并提取年、月、日 String namedGroupRegex = "(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})"; String dateInput = "今天是2024-05-20,昨天是2024-05-19,明天是2024-05-21"; Matcher namedMatcher = Pattern.compile(namedGroupRegex).matcher(dateInput); log.info("\n===== 命名分组匹配日期 ====="); while (namedMatcher.find()) { log.info("完整日期:'{}',年:'{}',月:'{}',日:'{}'", namedMatcher.group(0), namedMatcher.group("year"), namedMatcher.group("month"), namedMatcher.group("day")); } // 4. 正向预查:匹配后面紧跟@163.com的用户名 String positiveLookaheadRegex = "\\w+(?=@163\\.com)"; String emailInput = "user1@163.com user2@gmail.com user3@163.com"; Matcher positiveMatcher = Pattern.compile(positiveLookaheadRegex).matcher(emailInput); log.info("\n===== 正向预查匹配163邮箱用户名 ====="); while (positiveMatcher.find()) { log.info("匹配到用户名:'{}'", positiveMatcher.group()); } // 5. 负向后查:匹配前面不是http://的URL String negativeLookbehindRegex = "(?<!http://)\\w+\\.com"; String urlInput = "http://www.baidu.com www.google.com https://www.github.com www.163.com"; Matcher negativeMatcher = Pattern.compile(negativeLookbehindRegex).matcher(urlInput); log.info("\n===== 负向后查匹配非http开头的.com域名 ====="); while (negativeMatcher.find()) { log.info("匹配到域名:'{}'", negativeMatcher.group()); } } }运行结果:
===== 捕获组 (ab)+ 匹配 ===== 完整匹配:'ab',第1个分组(ab):'ab' 完整匹配:'abab',第1个分组(ab):'ab' 完整匹配:'ababab',第1个分组(ab):'ab' ===== 引用分组 (ab)c\1 匹配 ===== 完整匹配:'abcab',引用的分组内容:'ab' 完整匹配:'abcab',引用的分组内容:'ab' ===== 命名分组匹配日期 ===== 完整日期:'2024-05-20',年:'2024',月:'05',日:'20' 完整日期:'2024-05-19',年:'2024',月:'05',日:'19' 完整日期:'2024-05-21',年:'2024',月:'05',日:'21' ===== 正向预查匹配163邮箱用户名 ===== 匹配到用户名:'user1' 匹配到用户名:'user3' ===== 负向后查匹配非http开头的.com域名 ===== 匹配到域名:'www.google.com' 匹配到域名:'www.163.com'三、正则表达式进阶:Java中的正则引擎与核心API
掌握了正则表达式的语法后,我们需要结合Java的正则API进行实际开发。Java的正则引擎基于“有限自动机”实现,核心API位于java.util.regex包下,主要包括Pattern(正则表达式编译后的对象)和Matcher(匹配器对象)。
1. Java正则API核心类关系
2. 核心API详解
(1)Pattern类
Pattern是正则表达式的编译表示,线程安全,可复用(推荐复用,避免重复编译提升性能)。
核心方法:
static Pattern compile(String regex):编译正则表达式字符串,返回Pattern对象;static Pattern compile(String regex, int flags):带标志的编译(如Pattern.CASE_INSENSITIVE忽略大小写、Pattern.DOTALL让.匹配换行符);Matcher matcher(CharSequence input):创建匹配器对象,用于匹配输入字符串;static boolean matches(String regex, CharSequence input):静态方法,直接判断输入字符串是否匹配正则表达式(等价于compile(regex).matcher(input).matches());String[] split(CharSequence input):根据正则表达式分割输入字符串,返回字符串数组。
(2)Matcher类
Matcher是匹配器对象,非线程安全,用于执行具体的匹配操作。
核心方法:
boolean find():查找输入字符串中是否有匹配的子串(可多次调用,从上次匹配结束位置继续查找);boolean matches():判断整个输入字符串是否完全匹配正则表达式(等价于^regex$);boolean lookingAt():判断输入字符串的开头是否匹配正则表达式(等价于^regex);String group():返回当前匹配到的子串(等价于group(0));String group(int group):返回第n个捕获组的内容(n从1开始);String group(String name):返回命名捕获组的内容(Java 7+);int start():返回当前匹配子串的起始位置;int end():返回当前匹配子串的结束位置( exclusive);String replaceAll(String replacement):将所有匹配的子串替换为指定字符串;String replaceFirst(String replacement):将第一个匹配的子串替换为指定字符串;Matcher reset():重置匹配器,可重新从输入字符串开头查找。
(3)常用标志(flags)
| 标志常量 | 含义 | 简写 |
|---|---|---|
| Pattern.CASE_INSENSITIVE | 忽略大小写匹配(默认只匹配ASCII字符) | (?i) |
| Pattern.DOTALL | 让.匹配包括换行符在内的任意字符 | (?s) |
| Pattern.MULTILINE | 多行模式,^匹配行开头,$匹配行结尾 | (?m) |
| Pattern.UNICODE_CASE | 忽略大小写匹配(支持Unicode字符) | (?u) |
| Pattern.COMMENTS | 允许正则表达式中添加注释(#开头到行尾) | (?x) |
标志使用实例
package com.jam.demo.regex; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 正则标志使用示例 * @author ken */ @Slf4j public class RegexFlagsDemo { public static void main(String[] args) { // 1. CASE_INSENSITIVE:忽略大小写匹配 String regex1 = "abc"; String input1 = "AbC aBc ABC xyz"; Pattern pattern1 = Pattern.compile(regex1, Pattern.CASE_INSENSITIVE); Matcher matcher1 = pattern1.matcher(input1); log.info("===== 忽略大小写匹配 abc ====="); while (matcher1.find()) { log.info("匹配到:'{}'", matcher1.group()); } // 2. DOTALL:. 匹配换行符 String regex2 = "a.b"; String input2 = "a\nb a1b aab"; // 无DOTALL标志(不匹配换行符) Matcher matcher2 = Pattern.compile(regex2).matcher(input2); // 有DOTALL标志(匹配换行符) Matcher matcher2WithDotAll = Pattern.compile(regex2, Pattern.DOTALL).matcher(input2); log.info("\n===== 无DOTALL标志匹配 a.b ====="); while (matcher2.find()) { log.info("匹配到:'{}'", matcher2.group()); } log.info("\n===== 有DOTALL标志匹配 a.b ====="); while (matcher2WithDotAll.find()) { log.info("匹配到:'{}'", matcher2WithDotAll.group()); } // 3. 简写标志:在正则表达式中直接使用 (?i)(?s) 等 String regex3 = "(?i)abc"; // 等价于 Pattern.CASE_INSENSITIVE String input3 = "AbC ABC aBc"; Matcher matcher3 = Pattern.compile(regex3).matcher(input3); log.info("\n===== 简写标志 (?i)abc 匹配 ====="); while (matcher3.find()) { log.info("匹配到:'{}'", matcher3.group()); } } }运行结果:
===== 忽略大小写匹配 abc ===== 匹配到:'AbC' 匹配到:'aBc' 匹配到:'ABC' ===== 无DOTALL标志匹配 a.b ===== 匹配到:'a1b' 匹配到:'aab' ===== 有DOTALL标志匹配 a.b ===== 匹配到:'a b' 匹配到:'a1b' 匹配到:'aab' ===== 简写标志 (?i)abc 匹配 ===== 匹配到:'AbC' 匹配到:'ABC' 匹配到:'aBc'3. Java正则性能优化技巧
复用Pattern对象:
Pattern.compile()是耗时操作,若正则表达式固定,应将Pattern对象定义为静态常量,避免重复编译;优先使用非捕获组:若无需引用分组内容,使用
(?:pattern)而非(pattern),减少内存占用;避免过度使用贪婪匹配:贪婪匹配可能导致回溯过多,性能下降,必要时使用非贪婪匹配或更精准的正则;
使用预查替代不必要的分组:例如验证密码强度时,用正向预查
(?=.*[A-Z])替代捕获组,性能更优;限制匹配范围:尽量缩小正则表达式的匹配范围,避免无限制的
.*(例如用[^@]*匹配邮箱用户名,而非.*)。
四、实战场景:正则表达式在Java开发中的高频应用
结合前面讲解的语法和API,我们针对Java开发中的高频场景,提供可直接复用的实战代码。
1. 场景1:用户输入验证(手机号、邮箱、身份证号、密码)
需求:
手机号:11位数字,以13/14/15/17/18/19开头;
邮箱:符合
用户名@域名格式,用户名可包含字母、数字、下划线、点,域名可包含多级(如xxx.xxx.com);身份证号(18位):前6位为地址码,中间8位为出生日期(yyyyMMdd),后4位为顺序码和校验码(最后一位可为X);
密码强度:8-20位,包含大小写字母、数字、特殊字符(至少三种)。
实战代码:
package com.jam.demo.regex.validator; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.util.regex.Pattern; /** * 用户输入验证工具类(正则表达式实现) * @author ken */ @Slf4j public class RegexValidatorUtil { /** * 手机号正则(11位数字,以13/14/15/17/18/19开头) */ private static final Pattern MOBILE_PHONE_PATTERN = Pattern.compile("^1[345789]\\d{9}$"); /** * 邮箱正则(简化版,覆盖大部分场景) * 用户名:字母、数字、下划线、点、减号 * 域名:字母、数字、下划线、点、减号,至少包含一个点 */ private static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*\\.[a-zA-Z]{2,}$"); /** * 18位身份证号正则 */ private static final Pattern ID_CARD_18_PATTERN = Pattern.compile("^[1-9]\\d{5}(19|20)\\d{2}((0[1-9])|(1[0-2]))((0[1-9])|([12]\\d)|(3[01]))\\d{3}[0-9Xx]$"); /** * 密码强度正则(8-20位,包含大小写字母、数字、特殊字符至少三种) * 特殊字符:!@#$%^&*()_+-=[]{}|;':",./<>? */ private static final Pattern PASSWORD_STRONG_PATTERN = Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)|(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+-=[]{}|;':\",./<>?])|(?=.*[a-z])(?=.*\\d)(?=.*[!@#$%^&*()_+-=[]{}|;':\",./<>?])|(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*()_+-=[]{}|;':\",./<>?])).{8,20}$"); /** * 验证手机号 * @param mobile 手机号字符串 * @return true:验证通过,false:验证失败 */ public static boolean validateMobile(String mobile) { if (StringUtils.isEmpty(mobile)) { log.error("手机号验证失败:输入为空"); return false; } boolean isMatch = MOBILE_PHONE_PATTERN.matcher(mobile).matches(); if (!isMatch) { log.error("手机号验证失败:{} 格式不正确", mobile); } return isMatch; } /** * 验证邮箱 * @param email 邮箱字符串 * @return true:验证通过,false:验证失败 */ public static boolean validateEmail(String email) { if (StringUtils.isEmpty(email)) { log.error("邮箱验证失败:输入为空"); return false; } boolean isMatch = EMAIL_PATTERN.matcher(email).matches(); if (!isMatch) { log.error("邮箱验证失败:{} 格式不正确", email); } return isMatch; } /** * 验证18位身份证号 * @param idCard 身份证号字符串 * @return true:验证通过,false:验证失败 */ public static boolean validateIdCard18(String idCard) { if (StringUtils.isEmpty(idCard)) { log.error("身份证号验证失败:输入为空"); return false; } // 先验证格式 boolean isMatch = ID_CARD_18_PATTERN.matcher(idCard).matches(); if (!isMatch) { log.error("身份证号验证失败:{} 格式不正确", idCard); return false; } // (可选)验证校验码(身份证号最后一位) boolean checkCodeValid = validateIdCardCheckCode(idCard); if (!checkCodeValid) { log.error("身份证号验证失败:{} 校验码错误", idCard); return false; } return true; } /** * 验证密码强度 * @param password 密码字符串 * @return true:验证通过,false:验证失败 */ public static boolean validatePasswordStrong(String password) { if (StringUtils.isEmpty(password)) { log.error("密码验证失败:输入为空"); return false; } boolean isMatch = PASSWORD_STRONG_PATTERN.matcher(password).matches(); if (!isMatch) { log.error("密码验证失败:{} 不符合要求(8-20位,包含大小写字母、数字、特殊字符至少三种)", password); } return isMatch; } /** * 验证身份证号校验码(18位) * 校验规则:前17位数字加权求和,权重为7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2 * 求和结果对11取余,余数对应校验码:0-1,1-0,2-X,3-9,4-8,5-7,6-6,7-5,8-4,9-3,10-2 * @param idCard 18位身份证号 * @return true:校验码正确,false:校验码错误 */ private static boolean validateIdCardCheckCode(String idCard) { // 权重数组 int[] weights = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}; // 校验码对应表 char[] checkCodes = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}; // 计算前17位加权和 int sum = 0; for (int i = 0; i < 17; i++) { sum += (idCard.charAt(i) - '0') * weights[i]; } // 计算校验码 char expectedCheckCode = checkCodes[sum % 11]; // 比较校验码(忽略大小写) char actualCheckCode = Character.toUpperCase(idCard.charAt(17)); return actualCheckCode == expectedCheckCode; } // 测试方法 public static void main(String[] args) { log.info("手机号验证:13812345678 → {}", validateMobile("13812345678")); log.info("手机号验证:12345678901 → {}", validateMobile("12345678901")); log.info("\n邮箱验证:user1@163.com → {}", validateEmail("user1@163.com")); log.info("邮箱验证:user@.com → {}", validateEmail("user@.com")); log.info("\n身份证号验证:110101199001011234 → {}", validateIdCard18("110101199001011234")); log.info("身份证号验证:11010119900101123X → {}", validateIdCard18("11010119900101123X")); log.info("身份证号验证:110101199001011235 → {}", validateIdCard18("110101199001011235")); // 校验码错误 log.info("\n密码验证:Abc123!@# → {}", validatePasswordStrong("Abc123!@#")); log.info("密码验证:abc123456 → {}", validatePasswordStrong("abc123456")); // 缺少大写和特殊字符 } }运行结果:
手机号验证:13812345678 → true 手机号验证:12345678901 → false 邮箱验证:user1@163.com → true 邮箱验证:user@.com → false 身份证号验证:110101199001011234 → true 身份证号验证:11010119900101123X → true 身份证号验证:110101199001011235 → false 密码验证:Abc123!@# → true 密码验证:abc123456 → false2. 场景2:日志解析(提取日志中的时间、错误码、用户ID)
需求:
日志格式:
[2024-05-20 14:30:25.123] [ERROR] [userId:1001] [errorCode:500] - 数据库查询失败;提取字段:时间、日志级别、用户ID、错误码、错误信息。
实战代码:
package com.jam.demo.regex.logparse; import com.alibaba.fastjson2.JSON; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 日志解析工具类(正则表达式实现) * @author ken */ @Slf4j public class LogParseUtil { /** * 日志正则表达式(匹配格式:[时间] [级别] [userId:xxx] [errorCode:xxx] - 信息) */ private static final Pattern LOG_PATTERN = Pattern.compile( "^\\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3})\\] \\[(\\w+)\\] \\[userId:(\\d+)\\] \\[errorCode:(\\d+)\\] - (.*)$" ); /** * 解析日志字符串 * @param logStr 日志字符串 * @return LogInfo 日志信息对象(解析失败返回null) */ public static LogInfo parseLog(String logStr) { if (StringUtils.isEmpty(logStr)) { log.error("日志解析失败:输入日志为空"); return null; } Matcher matcher = LOG_PATTERN.matcher(logStr); if (!matcher.matches()) { log.error("日志解析失败:日志格式不匹配,日志内容:{}", logStr); return null; } // 提取分组内容 LogInfo logInfo = new LogInfo(); logInfo.setTime(matcher.group(1)); logInfo.setLevel(matcher.group(2)); logInfo.setUserId(matcher.group(3)); logInfo.setErrorCode(matcher.group(4)); logInfo.setMessage(matcher.group(5)); return logInfo; } // 日志信息封装类 @Data public static class LogInfo { /** 日志时间 */ private String time; /** 日志级别(INFO/ERROR/WARN/DEBUG) */ private String level; /** 用户ID */ private String userId; /** 错误码 */ private String errorCode; /** 日志信息 */ private String message; } // 测试方法 public static void main(String[] args) { String log1 = "[2024-05-20 14:30:25.123] [ERROR] [userId:1001] [errorCode:500] - 数据库查询失败"; String log2 = "[2024-05-20 15:40:10.456] [INFO] [userId:1002] [errorCode:0] - 用户登录成功"; String log3 = "2024-05-20 16:50:30.789 ERROR userId:1003 errorCode:404 页面不存在"; // 格式错误 LogInfo logInfo1 = parseLog(log1); LogInfo logInfo2 = parseLog(log2); LogInfo logInfo3 = parseLog(log3); log.info("日志1解析结果:{}", JSON.toJSONString(logInfo1)); log.info("日志2解析结果:{}", JSON.toJSONString(logInfo2)); log.info("日志3解析结果:{}", logInfo3); } }运行结果:
日志1解析结果:{"errorCode":"500","level":"ERROR","message":"数据库查询失败","time":"2024-05-20 14:30:25.123","userId":"1001"} 日志2解析结果:{"errorCode":"0","level":"INFO","message":"用户登录成功","time":"2024-05-20 15:40:10.456","userId":"1002"} 日志解析失败:日志格式不匹配,日志内容:2024-05-20 16:50:30.789 ERROR userId:1003 errorCode:404 页面不存在 日志3解析结果:null3. 场景3:字符串替换与格式化(批量替换、脱敏处理)
需求:
批量替换:将字符串中的所有手机号中间4位替换为
****(脱敏);格式标准化:将字符串中的日期格式统一为
yyyy-MM-dd(如将2024/05/20、2024.05.20转换为2024-05-20);去除空格:去除字符串中的所有空白字符(空格、制表符、换行符)。
实战代码:
package com.jam.demo.regex.replace; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 字符串替换与格式化工具类(正则表达式实现) * @author ken */ @Slf4j public class StringReplaceUtil { /** * 手机号脱敏正则(匹配11位手机号,捕获前3位和后4位) */ private static final Pattern MOBILE_MASK_PATTERN = Pattern.compile("(1[345789])(\\d{4})(\\d{4})"); /** * 日期格式标准化正则(匹配 yyyy/MM/dd 或 yyyy.MM.dd 格式) */ private static final Pattern DATE_NORMALIZE_PATTERN = Pattern.compile("(\\d{4})[/.](\\d{2})[/.](\\d{2})"); /** * 空白字符匹配正则(匹配所有空白字符:空格、制表符、换行符、回车符) */ private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); /** * 手机号脱敏:中间4位替换为**** * @param input 包含手机号的字符串 * @return 脱敏后的字符串 */ public static String maskMobile(String input) { if (StringUtils.isEmpty(input)) { log.warn("手机号脱敏失败:输入字符串为空"); return input; } // 使用分组引用替换,$1表示前3位,$3表示后4位 return MOBILE_MASK_PATTERN.matcher(input).replaceAll("$1****$3"); } /** * 日期格式标准化:将 yyyy/MM/dd 或 yyyy.MM.dd 转换为 yyyy-MM-dd * @param input 包含日期的字符串 * @return 日期格式标准化后的字符串 */ public static String normalizeDate(String input) { if (StringUtils.isEmpty(input)) { log.warn("日期标准化失败:输入字符串为空"); return input; } // 分组引用替换,$1=年,$2=月,$3=日 return DATE_NORMALIZE_PATTERN.matcher(input).replaceAll("$1-$2-$3"); } /** * 去除所有空白字符(空格、制表符、换行符、回车符) * @param input 输入字符串 * @return 去除空白后的字符串 */ public static String removeAllWhitespace(String input) { if (StringUtils.isEmpty(input)) { log.warn("去除空白失败:输入字符串为空"); return input; } return WHITESPACE_PATTERN.matcher(input).replaceAll(""); } /** * 测试方法 * @param args 命令行参数 */ public static void main(String[] args) { // 测试手机号脱敏 String mobileStr = "联系电话:13812345678 或 13987654321,备用电话:15011112222"; String maskedMobile = maskMobile(mobileStr); log.info("手机号脱敏前:{}", mobileStr); log.info("手机号脱敏后:{}", maskedMobile); // 测试日期格式标准化 String dateStr = "今天是2024/05/20,昨天是2024.05.19,明天是2024-05-21"; String normalizedDate = normalizeDate(dateStr); log.info("\n日期标准化前:{}", dateStr); log.info("日期标准化后:{}", normalizedDate); // 测试去除所有空白字符 String whitespaceStr = " hello \t world \n java \r regex "; String noWhitespace = removeAllWhitespace(whitespaceStr); log.info("\n去除空白前:{}", whitespaceStr); log.info("去除空白后:{}", noWhitespace); } }运行结果:
手机号脱敏前:联系电话:13812345678 或 13987654321,备用电话:15011112222 手机号脱敏后:联系电话:138****5678 或 139****4321,备用电话:150****2222 日期标准化前:今天是2024/05/20,昨天是2024.05.19,明天是2024-05-21 日期标准化后:今天是2024-05-20,昨天是2024-05-19,明天是2024-05-21 去除空白前: hello world java regex 去除空白后:helloworldjavaregex4. 场景4:MySQL中的正则表达式应用(REGEXP运算符)
在MySQL中,REGEXP(或RLIKE)运算符用于执行正则表达式匹配,适用于复杂的模糊查询场景,功能比LIKE更强大。
语法:
SELECT column1, column2 FROM table_name WHERE column REGEXP 'regex_pattern';关键说明:
MySQL的正则表达式默认不区分大小写,若需区分大小写,使用
REGEXP BINARY;^匹配字符串开头,$匹配字符串结尾,.匹配任意单个字符;*匹配0次或多次,+匹配1次或多次,?匹配0次或1次;[]匹配括号内的任意字符,[^]匹配括号外的任意字符。
实战案例(MySQL 8.0环境验证)
准备测试表和数据
-- 创建用户表 DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` VARCHAR(50) NOT NULL COMMENT '用户名', `mobile` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户测试表'; -- 插入测试数据 INSERT INTO `t_user` (`username`, `mobile`, `email`) VALUES ('zhangsan', '13812345678', 'zhangsan@163.com'), ('lisi', '13987654321', 'lisi@gmail.com'), ('wangwu', '15011112222', 'wangwu@qq.com'), ('zhaoliu', '18099998888', 'zhaoliu@company.com'), ('tianqi', '02012345678', 'tianqi@126.com');案例1:查询手机号以138或139开头的用户
SELECT id, username, mobile FROM t_user WHERE mobile REGEXP '^13[89]';查询结果:
| id | username | mobile |
|---|---|---|
| 1 | zhangsan | 13812345678 |
| 2 | lisi | 13987654321 |
案例2:查询邮箱为163或126域名的用户
SELECT id, username, email FROM t_user WHERE email REGEXP '@(163|126)\\.com$';查询结果:
| id | username | |
|---|---|---|
| 1 | zhangsan | zhangsan@163.com |
| 5 | tianqi | tianqi@126.com |
案例3:查询用户名包含字母且长度为6的用户(区分大小写)
SELECT id, username FROM t_user WHERE username REGEXP BINARY '^[a-zA-Z]{6}$';查询结果:
| id | username |
|---|---|
| 1 | zhangsan |
| 2 | lisi |
| 3 | wangwu |
| 4 | zhaoliu |
案例4:查询手机号不符合11位规范的用户
SELECT id, username, mobile FROM t_user WHERE mobile NOT REGEXP '^1[345789]\\d{9}$';查询结果:
| id | username | mobile |
|---|---|---|
| 5 | tianqi | 02012345678 |
五、正则表达式避坑指南:易混淆点与常见错误
1. 易混淆点明确区分
| 易混淆语法 | 正确含义 | 错误理解 | 典型错误示例 |
|---|---|---|---|
^和$ | 字符串开头/结尾(多行模式为行开头/结尾) | 匹配字符^或$ | 用^abc$匹配包含abc的字符串(实际是精准匹配abc) |
. | 匹配任意单个字符(除换行符) | 匹配字符. | 用a.b匹配a.b(正确写法应为a\\.b) |
\d和[0-9] | 在Java中等价,匹配数字字符 | \d匹配任意十进制数 | 无差异,但需注意转义:Java中写\\d,正则原生写\d |
| 贪婪匹配 vs 非贪婪匹配 | 贪婪:尽可能多匹配;非贪婪:尽可能少匹配 | 非贪婪匹配速度一定更快 | a.*b匹配aabbaacbb时贪婪匹配整个字符串,非贪婪匹配aab |
| 捕获组 vs 非捕获组 | 捕获组(pattern)可引用,非捕获组(?:pattern)不可引用 | 非捕获组无法分组 | 无需引用分组时用非捕获组,提升性能 |
2. 常见错误及解决方案
错误1:Java中忘记转义反斜杠
错误代码:
// 错误:Java中\需要转义为\\,否则编译报错 Pattern pattern = Pattern.compile("\d{3}");解决方案:Java字符串中反斜杠需要转义,正则中的\d在Java中需写为\\d。
// 正确写法 Pattern pattern = Pattern.compile("\\d{3}");错误2:用matches()方法进行部分匹配
错误代码:
// 需求:判断字符串是否包含数字,错误使用matches() String input = "abc123def"; boolean hasNumber = Pattern.matches("\\d+", input); // 返回false解决方案:matches()方法判断整个字符串是否匹配,部分匹配应使用find()方法。
boolean hasNumber = Pattern.compile("\\d+").matcher(input).find(); // 返回true错误3:正则表达式过于复杂导致回溯爆炸
错误场景:用(a+)+b匹配超长字符串aaaaaaaaaaaaaaaaaaaaaaaaaaaaa(无b结尾),导致程序卡顿。解决方案:优化正则表达式,避免嵌套量词,缩小匹配范围。
// 优化后:避免嵌套量词 Pattern pattern = Pattern.compile("a+b");错误4:忽略Pattern的线程安全性
错误代码:
// 每次匹配都编译Pattern,性能低下且浪费资源 public boolean isMobile(String input) { return Pattern.compile("^1[345789]\\d{9}$").matcher(input).matches(); }解决方案:将Pattern定义为静态常量,复用编译后的对象。
private static final Pattern MOBILE_PATTERN = Pattern.compile("^1[345789]\\d{9}$"); public boolean isMobile(String input) { return MOBILE_PATTERN.matcher(input).matches(); }六、正则表达式性能优化与最佳实践
1. 性能优化技巧
(1)复用Pattern对象
Pattern.compile()是耗时操作,正则表达式固定时,应将Pattern定义为静态常量,避免重复编译。
(2)优先使用非捕获组
当不需要引用分组内容时,使用非捕获组(?:pattern)替代捕获组(pattern),减少内存占用和匹配时间。
(3)避免过度使用贪婪匹配
贪婪匹配可能导致大量回溯,必要时使用非贪婪匹配或更精准的正则表达式。例如:
不好的写法:
a.*b(匹配a到最后一个b)更好的写法:
a[^b]*b(匹配a到第一个b,无回溯)
(4)限制匹配范围
尽量使用精准的字符集替代宽泛的匹配。例如:
匹配邮箱用户名:用
[a-zA-Z0-9_-]+替代.*匹配日期:用
\\d{4}-\\d{2}-\\d{2}替代.*
(5)使用预查替代不必要的分组
预查(零宽断言)只匹配位置,不消耗字符,性能优于分组。例如验证密码强度:
// 正向预查:密码包含大写字母、小写字母、数字 String regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,20}$";2. 最佳实践
(1)明确正则表达式的适用场景
正则表达式适合处理结构化字符串(如手机号、邮箱、日期),不适合处理非结构化字符串(如HTML/XML解析,推荐使用专门的解析库)。
(2)编写可维护的正则表达式
复杂正则表达式添加注释(使用
Pattern.COMMENTS标志,注释以#开头);拆分复杂正则为多个简单正则,分步匹配。
(3)测试正则表达式的边界情况
针对以下边界情况进行测试:
空字符串;
最大长度字符串;
临界值(如手机号10位、12位,身份证号17位、19位)。
(4)使用工具辅助编写正则表达式
推荐工具:
Regex101:在线正则表达式测试工具,支持Java、Python等多种语言;
RegexBuddy:桌面端正则表达式编辑和测试工具,适合复杂正则编写。
七、总结
正则表达式是处理字符串的“瑞士军刀”,掌握其语法和底层逻辑,能大幅提升字符串处理的效率和准确性。本文从基础语法、Java核心API、实战场景、避坑指南、性能优化五个维度,全面讲解了正则表达式的知识体系,结合JDK 17和MySQL 8.0环境下的可运行实例,帮助读者夯实基础、解决实际问题。