正则表达式从入门到精通:吃透底层逻辑,解决99%的实际问题

简介: 正则表达式是高效处理字符串的利器,广泛应用于数据校验、日志解析、文本替换等场景。本文系统讲解其核心语法、Java实现及实战应用,结合JDK 17与MySQL 8.0实例,助你掌握从基础到进阶的完整知识体系,提升开发效率。

正则表达式(Regular Expression)是一种用于描述字符串模式的强大工具,广泛应用于字符串匹配、查找、替换、验证等场景。无论是Java开发中的数据校验、日志解析,还是SQL中的模糊查询,亦或是日常的文本处理,掌握正则表达式都能大幅提升效率。本文将从底层逻辑出发,由浅入深、系统全面地讲解正则表达式,结合JDK 17环境下的可运行实例,帮你彻底吃透这门技术。

一、为什么要学正则表达式?底层价值与应用场景

在讲解具体语法之前,我们先搞清楚:正则表达式的核心价值是什么?为什么不直接用字符串的indexOfcontains等方法?

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. 编译正则表达式:将正则表达式字符串转换为高效的匹配引擎(有限自动机);
  2. 输入字符串扫描:匹配引擎按顺序扫描输入字符串的每个字符;
  3. 模式匹配校验:判断当前扫描位置的字符是否符合正则表达式定义的模式;
  4. 匹配结果返回:若匹配成功,返回匹配到的子串、位置等信息;若失败,返回无匹配。

流程图如下:

image.png

二、正则表达式基础语法:吃透核心元字符

正则表达式的语法核心是“元字符”——具有特殊含义的字符。掌握元字符的含义和用法,是学习正则表达式的基础。我们将元字符分为“基础匹配元字符”“量词元字符”“边界匹配元字符”“分组与引用元字符”四类,逐一讲解。

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,而非aab);
  • 非贪婪匹配:在量词后加?,尽可能匹配最少的字符(例如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~29

4. 分组与引用元字符(匹配多个字符的组合)

当需要将多个字符作为一个整体(组合)进行匹配时,需要使用“分组”元字符。分组还支持“引用”(重复匹配已匹配的分组内容)和“命名分组”(更清晰地获取分组结果)。

元字符 含义 示例 匹配结果
(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核心类关系

image.png

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正则性能优化技巧

  1. 复用Pattern对象Pattern.compile()是耗时操作,若正则表达式固定,应将Pattern对象定义为静态常量,避免重复编译;
  2. 优先使用非捕获组:若无需引用分组内容,使用(?:pattern)而非(pattern),减少内存占用;
  3. 避免过度使用贪婪匹配:贪婪匹配可能导致回溯过多,性能下降,必要时使用非贪婪匹配或更精准的正则;
  4. 使用预查替代不必要的分组:例如验证密码强度时,用正向预查(?=.*[A-Z])替代捕获组,性能更优;
  5. 限制匹配范围:尽量缩小正则表达式的匹配范围,避免无限制的.*(例如用[^@]*匹配邮箱用户名,而非.*)。

四、实战场景:正则表达式在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 → false

2. 场景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解析结果:null

3. 场景3:字符串替换与格式化(批量替换、脱敏处理)

需求:

  • 批量替换:将字符串中的所有手机号中间4位替换为****(脱敏);
  • 格式标准化:将字符串中的日期格式统一为yyyy-MM-dd(如将2024/05/202024.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  
去除空白后:helloworldjavaregex

4. 场景4:MySQL中的正则表达式应用(REGEXP运算符)

在MySQL中,REGEXP(或RLIKE)运算符用于执行正则表达式匹配,适用于复杂的模糊查询场景,功能比LIKE更强大。

语法:

SELECT column1, column2 FROM table_name WHERE column REGEXP 'regex_pattern';

关键说明:

  1. MySQL的正则表达式默认不区分大小写,若需区分大小写,使用REGEXP BINARY
  2. ^匹配字符串开头,$匹配字符串结尾,.匹配任意单个字符;
  3. *匹配0次或多次,+匹配1次或多次,?匹配0次或1次;
  4. []匹配括号内的任意字符,[^]匹配括号外的任意字符。

实战案例(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 email
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环境下的可运行实例,帮助读者夯实基础、解决实际问题。

正则表达式的学习没有捷径,关键在于多练、多测、多优化。希望本文能成为你开发路上的实用指南,让你在处理字符串场景时游刃有余。

是否需要我帮你整理正则表达式高频场景的工具类清单,方便你直接集成到项目中?

目录
相关文章
|
8天前
|
云安全 监控 安全
|
13天前
|
机器学习/深度学习 人工智能 自然语言处理
Z-Image:冲击体验上限的下一代图像生成模型
通义实验室推出全新文生图模型Z-Image,以6B参数实现“快、稳、轻、准”突破。Turbo版本仅需8步亚秒级生成,支持16GB显存设备,中英双语理解与文字渲染尤为出色,真实感和美学表现媲美国际顶尖模型,被誉为“最值得关注的开源生图模型之一”。
1432 8
|
7天前
|
人工智能 安全 前端开发
AgentScope Java v1.0 发布,让 Java 开发者轻松构建企业级 Agentic 应用
AgentScope 重磅发布 Java 版本,拥抱企业开发主流技术栈。
471 11
|
19天前
|
人工智能 Java API
Java 正式进入 Agentic AI 时代:Spring AI Alibaba 1.1 发布背后的技术演进
Spring AI Alibaba 1.1 正式发布,提供极简方式构建企业级AI智能体。基于ReactAgent核心,支持多智能体协作、上下文工程与生产级管控,助力开发者快速打造可靠、可扩展的智能应用。
1255 43
|
19天前
|
人工智能 前端开发 算法
大厂CIO独家分享:AI如何重塑开发者未来十年
在 AI 时代,若你还在紧盯代码量、执着于全栈工程师的招聘,或者仅凭技术贡献率来评判价值,执着于业务提效的比例而忽略产研价值,你很可能已经被所谓的“常识”困住了脚步。
1162 88
大厂CIO独家分享:AI如何重塑开发者未来十年
|
1天前
|
存储 弹性计算 容灾
阿里云服务器ECS自定义购买流程:超详细新手入门教程
本文详细介绍阿里云服务器ECS自定义购买全流程,涵盖付费模式、地域选择、网络配置、实例规格、镜像系统、存储、公网IP、带宽计费及安全组设置等关键步骤,适合新手入门参考,助你轻松完成云服务器选购与部署。
194 121