Java正则表达式快速入门

✅ 一、字符类匹配(Character Classes)

表达式含义示例匹配
.任意单个字符(不包括换行)a1$
[abc]匹配 a、b 或 c 中的任意一个字符abc
[^abc]匹配非 a、b、c 的字符d1
[a-z]匹配 a 到 z 的任意小写字母gm
[A-Z]匹配 A 到 Z 的大写字母QM
[0-9]匹配任意一位数字37
[a-zA-Z0-9_]匹配字母、数字或下划线xZ0

✅ 二、预定义字符类

表达式含义示例
\d数字,等同于 [0-9]05
\D非数字,等同于 [^0-9]a$
\w字母数字下划线,等同于 [a-zA-Z0-9_]a8_
\W非字母数字下划线#空格
\s空白字符(空格、制表、换行)' '\n
\S非空白字符x9

⚠ Java 字符串中需双重转义:如写 \\d 才是正则的 \d


✅ 三、数量词(重复)

表达式含义匹配示例
?重复0次或1次a?""a
+重复1次或多次a+aaa
*重复0次或多次a*""aaaa
{n}重复n次a{3}aaa
{n,}重复至少n次a{2,}aaaaaaa
{n,m}重复n到m次a{2,4}aaaaa

✅ 四、边界匹配符

表达式含义示例
^匹配输入的开始位置^abc → 匹配以 abc 开头
$匹配输入的结束位置abc$ → 匹配以 abc 结尾
\b单词边界\bword\b 匹配完整单词
\B非单词边界\Bend → 不在边界处匹配

✅ 五、分组与捕获

分组的主要作用:

1.将一段模式作为一个整体进行操作(比如重复、量词作用于整个分组)。

例子:匹配重复出现的 “abc”

(abc)+
2.组织正则表达式结构,使表达式更清晰。

例子:匹配电话号码(带区号和连字符)

(\d{3})-(\d{3})-(\d{4})
3. 分组配合选择符 |

例子:匹配重复出现的 “abc”

(abc)+
  • 说明:括号把 "abc" 作为一个整体
  • 量词 + 作用于整个分组,表示 "abc" 至少出现一次,可以连续出现多次
  • 匹配:abcabcabcabcabcabc
表达式含义示例
(abc)匹配并捕获 abc(ab)+abab
(?:abc)匹配但不捕获组用于性能优化
`(ab)`匹配 a 或 b
\1, \2...引用前面第几个捕获组重复匹配 (\w+)\s\1
解释:匹配一个单词(用 (\w+) 捕获),后面跟一个空格,再跟着和第一个捕获组完全相同的单词
字符串 "hello hello" 会匹配,因为第二个单词和第一个单词相同
字符串 "hello world" 不匹配

✅ 六、零宽断言(高级)

表达式含义示例
(?=abc)正向先行断言,后面是 abca(?=b)→ 匹配 a,后面必须是 b
(?!abc)负向先行断言,后面不是 abca(?!b)→ 匹配 a,后面不能是 b
(?<=abc)正向后行断言,前面是 abc(?<=a)b→ 匹配 b,前面必须是 a
(?<!abc)负向后行断言,前面不是 abc(?<!a)b→ 匹配 b,前面不能是 a

注意:Java 从 9 开始才支持 (?<=...)后行断言


✅ 七、贪婪模式(Greedy)

定义

  • 贪婪模式是正则表达式默认的匹配方式。
  • 它会尝试匹配尽可能多的字符,直到整个表达式无法再匹配更长字符串为止。
  • 它满足匹配条件后不会停止,而是尽量往后多匹配。

量词符号(贪婪)

量词含义
*0 次或多次
+1 次或多次
?0 次或 1 次
{n,}至少 n 次

例子

字符串:

css


复制编辑
"<div>content</div><div>more</div>"

正则:

regex


复制编辑
<.*>
  • .* 是贪婪匹配,尽可能多匹配。
  • 匹配结果是:
css


复制编辑
<div>content</div><div>more</div>

整个字符串从第一个 < 到最后一个 > 都被匹配了。


2. 非贪婪模式(Lazy / Reluctant)

定义

  • 非贪婪模式尝试匹配尽可能少的字符,满足整个表达式即可停止匹配。
  • *?, +?, ??, {n,m}? 来表示非贪婪量词。

量词符号(非贪婪)

量词含义
*?0 次或多次,尽量少匹配
+?1 次或多次,尽量少匹配
??0 次或 1 次,尽量少匹配
{n,m}?匹配 n 到 m 次,尽量少匹配

例子

字符串:

"<div>content</div><div>more</div>"

正则:

<.*?>
  • .*? 是非贪婪匹配,尽量少匹配字符。
  • 匹配结果:
<div>
<div>

匹配到的第一个 <div> 标签结束的位置就停下来。


3. 两者对比总结

模式匹配原则示例匹配结果
贪婪模式尽可能多匹配<div>content</div><div>more</div>
非贪婪模式尽可能少匹配<div>
<div>

4. Java 示例代码

    public static void greedyVsLazy() {
        String text = "<div>content</div><div>more</div>";

        // 贪婪模式
        Pattern greedy = Pattern.compile("<.*>");
        Matcher greedyMatcher = greedy.matcher(text);
        if (greedyMatcher.find()) {
            System.out.println("贪婪匹配: " + greedyMatcher.group());
        }

        // 非贪婪模式
        Pattern lazy = Pattern.compile("<.*?>");
        Matcher lazyMatcher = lazy.matcher(text);
        System.out.println("非贪婪匹配:");
        while (lazyMatcher.find()) {
            System.out.println(lazyMatcher.group());
        }
    }

输出结果

贪婪匹配: <div>content</div><div>more</div>
非贪婪匹配:
<div>
</div>
<div>
</div>

5. 什么时候用哪种?

  • 贪婪模式:适合你想匹配尽可能长的字符串,比如抓取从某个标记开始到最后的所有内容。
  • 非贪婪模式:适合你想匹配最近的结束符,比如匹配HTML标签、括号内容等。

✅ 八、常见正则表达式示例(Java用)

目的正则表达式
手机号(中国)^1[3-9]\\d{9}$
邮箱地址^[\\w.-]+@[\\w.-]+\\.\\w{2,}$
身份证号码^\\d{15}(\\d{2}[0-9Xx])?$
IP 地址^(\\d{1,3}\\.){3}\\d{1,3}$
密码强度检测^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$
日期格式^\\d{4}-\\d{2}-\\d{2}$

✅ 九、Java 正则常用 API 详解

Java 正则相关的核心类主要在 java.util.regex 包里,主要有两个:

  • Pattern —— 编译正则表达式,生成模式对象
  • Matcher —— 对目标字符串进行匹配操作

1. Pattern

方法说明
Pattern.compile(String regex)编译正则表达式,返回 Pattern对象
Pattern.compile(String regex, int flags)编译正则表达式,并带有修饰符(如忽略大小写等)
pattern.matcher(CharSequence input)创建一个匹配器 Matcher,用来对输入字符串进行匹配
pattern.split(CharSequence input)按正则表达式分割字符串,返回字符串数组

2. Matcher

方法说明
matcher.find()查找下一个匹配,返回 truefalse
matcher.matches()判断整个字符串是否完全匹配正则表达式
matcher.group()获取整个匹配的子串(等价于 group(0)
matcher.group(int group)获取第 group个捕获组匹配到的子串
matcher.start()返回当前匹配的开始索引
matcher.end()返回当前匹配的结束索引
matcher.replaceAll(String replacement)将所有匹配替换为给定字符串
matcher.replaceFirst(String replacement)将第一个匹配替换为给定字符串
matcher.reset()重置匹配器,重新开始匹配

3. 例子演示

package com.omy.regex;

import jdk.nashorn.internal.ir.IfNode;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexExample1 {

    //普通示例
    static void func1() {
        String input = "abc123xyz456";
        //匹配连续出现1次或多次的数字
        String regex = "\\d+";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);

        while (matcher.find()) {
            System.out.println("匹配结果: " + matcher.group());
        }
    }

    //分组示例
    static void func2() {
        String text = "2025-05-22";
        Pattern pattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
        Matcher matcher = pattern.matcher(text);
        if (matcher.find()) {
            System.out.println(matcher.group());   // 输出整个匹配: 2025-05-22
            System.out.println(matcher.group(1));  // 输出第1个捕获组: 2025(年)
            System.out.println(matcher.group(2));  // 输出第2个捕获组: 05(月份)
            System.out.println(matcher.group(3));  // 输出第3个捕获组: 22(日)
        }
    }

    //分组示例
    static void func3() {
        String input = "hello hello";
        //分组示例:重复的单词,中间有一个空白字符。
        String regex = "(\\w+)\\s\\1";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);
        while (matcher.find()) {
            System.out.println("匹配结果: " + matcher.group());
            System.out.println("匹配第1组: " + matcher.group(1));
            // System.out.println("匹配第2组: " + matcher.group(2)); // \1 不能作为分组
        }
    }

    //边界匹配符 \b 示例
    static void func4() {
        String str = "testing bringing resting";
        /**
         * (\\w+) :
         * 捕获一个或多个字母、数字或下划线(即单词字符),这是第 1 个捕获组。它会匹配单词中 "ing" 之前的部分。
         * (?=ing\\b) :
         * 正向预测先行断言(Lookahead)
         * 表示后面紧跟着 "ing" 并且后面是单词边界(\\b),但“ing”不属于捕获内容。
         * 换句话说,匹配的是紧接着后面有 "ing" 的单词前半部分。
         */
        Pattern pattern = Pattern.compile("(\\w+)(?=ing\\b)");
        Matcher matcher = pattern.matcher(str);

        while (matcher.find()) {
            System.out.println(matcher.group(1));
        }
    }

    //只匹配以 "abc" 开头的字符串(后面可以跟任意内容)
    static void func5() {
        String regex = "^abc.*";
        String[] testStrings = {"abc123", "abcdef", "aabc", "123abc"};

        for (String s : testStrings) {
            if (s.matches(regex)) {
                System.out.println(s + " 匹配");
            } else {
                System.out.println(s + " 不匹配");
            }
        }

    }

    //综合案例
    static void func6() {
        String text = "Email: test@example.com and admin@abc.com";
        String regex = "(\\w+)@(\\w+\\.\\w+)";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);

        while (matcher.find()) {
            System.out.println("邮箱完整地址: " + matcher.group());    // 整个邮箱地址
            System.out.println("邮箱用户名: " + matcher.group(1));      // 捕获组1:用户名
            System.out.println("邮箱域名: " + matcher.group(2));        // 捕获组2:域名
            System.out.println("匹配结果在原串中起始位置: " + matcher.start() + "-" + matcher.end());
            System.out.println();
            System.out.println();
        }
    }

    public static void greedyVsLazy() {
        String text = "<div>content</div><div>more</div>";

        // 贪婪模式
        Pattern greedy = Pattern.compile("<.*>");
        Matcher greedyMatcher = greedy.matcher(text);
        if (greedyMatcher.find()) {
            System.out.println("贪婪匹配: " + greedyMatcher.group());
        }

        // 非贪婪模式
        Pattern lazy = Pattern.compile("<.*?>");
        Matcher lazyMatcher = lazy.matcher(text);
        System.out.println("非贪婪匹配:");
        while (lazyMatcher.find()) {
            System.out.println(lazyMatcher.group());
        }
    }

    public static void main(String[] args) {
        func1();
        System.out.println("====================分割线======================");
        func2();
        System.out.println("====================分割线======================");
        func3();
        System.out.println("====================分割线======================");
        func4();
        System.out.println("====================分割线======================");
        func5();
        System.out.println("====================分割线======================");
        func6();
        System.out.println("====================分割线======================");
        greedyVsLazy();
    }
}


4. 常用修饰符(flags)

  • Pattern.CASE_INSENSITIVE:忽略大小写
  • Pattern.MULTILINE:多行模式,^$ 匹配每行的开始和结束
  • Pattern.DOTALL:使 . 匹配换行符
  • Pattern.UNICODE_CASE:使用 Unicode 规则忽略大小写

✅ 十、正则表达式性能优化指南正则表达式性能优化指南

1. 减少回溯(Backtracking)

  • 避免使用“贪婪量词 + 可选重复”叠加,例如 (a+)+(.*)*,这些容易导致大量回溯,严重影响性能。
  • 使用 非贪婪量词 或限定重复次数,避免不确定性的叠加。
  • 示例:
    • 避免:(.*a)*
    • 推荐:明确限定重复次数,或者拆分成多个步骤。

2. 使用合理的量词

  • 避免使用 .*.+ 过于宽泛匹配,尤其是在多行内容或大文本中。
  • 通过更具体的字符集替代 .,如 [a-zA-Z0-9] 等,提高匹配速度。

3. 使用原子组(Atomic Grouping)和禁止回溯(Possessive Quantifiers)

  • Java 支持原子组 (?>...),避免回溯:
(?>a+)b
  • 还有 占有量词,例如 a++.*+,匹配时不允许回溯,减少性能损耗。

4. 提前排除不匹配字符

  • 使用字符类和限定符,避免宽泛匹配。
  • 例如匹配数字,不用 .,用 \d;匹配特定范围,使用 [a-z]

5. 合理使用断言

  • 断言(正向/反向预测)不会消耗匹配结果,但滥用复杂断言可能导致性能下降。
  • 保持断言简单和明确。

6. 预编译正则表达式

  • 尽量 复用 **Pattern** 对象,避免在循环内反复编译正则。
  • Pattern.compile() 是有成本的,复用能提高性能。

7. 使用 matches()find() 的区别

  • matches() 匹配整个字符串,适合验证格式,耗时相对较大。
  • find() 匹配子串,适合查找,通常更高效。

8. 避免过度嵌套分组

  • 多层分组增加匹配复杂度,合理简化分组结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值