Java正则表达式

Author Avatar
邬程峰
发表:2026-04-29 10:23:42
修改:2026-04-29 15:34:25

正则表达式的应用场景

最近(2025.8)在看《阿里巴巴 Java 开发手册》,其中有一条提到了正则表达式,这让我死去的记忆又复活了。

正则表达式(Regular Expressions)是一种强大的文本处理工具,广泛运用于对字符串、文本的操作场景,如搜索、编辑或者处理文本,它不是一种语言,而是一种文本模式,在每一种编程语言中的具体实现是有细微差别的,Java 中提供了 java.util.regex 包,它包含了 Pattern 和 Matcher 类,用于处理正则表达式的匹配操作。

正则表达式的语法

基础匹配符号

  1. 英文句号 .:匹配单个任何字符(除换行符外)

    • 示例:t.n 可以匹配 tentant1nt*n 等,但不能匹配 taantnt然n

  2. 方括号 []:只有方括号内指定的字符才参与匹配,匹配单个字符

    • 示例:t[abcd]n 可以匹配 tantbntcntdn,但不能匹配 thntabntn

  3. 或符号 |:相当于“或”,选择其中一项进行匹配

    • 示例:t(a|b|c|dd)n 可以匹配 tantbntcntddn,但不能匹配 taantntabcn

  4. 范围表示 -:在方括号内表示字符范围

    • 示例:[A-Z] 匹配任意大写字母,[a-z] 匹配任意小写字母,[0-9] 匹配任意数字

    • 注意:连字符 - 只有在字符类内部且不在首位时才表示范围,否则就是字面量

  5. 排除符号 ^:在方括号内首位使用时表示排除

    • 示例:[^abc] 匹配除了 a、b、c 以外的任意字符

    • 注意:方括号外的 ^ 表示字符串开始位置

表示匹配次数的符号

符号

含义

等价写法

示例

{n}

匹配表达式重复 n 次

-

\w{2} 等价于 \w\w

{n,m}

至少重复 n 次,至多重复 m 次

-

\d{2,4} 匹配 2-4 位数字

{n,}

至少重复 n 次

-

[a-z]{3,} 匹配至少 3 个小写字母

?

匹配 0 次或 1 次

{0,1}

colou?r 匹配 colorcolour

+

匹配至少 1 次

{1,}

go+gle 匹配 goglegooglegooogle

*

匹配 0 次或无限次

{0,}

go*gle 匹配 gglegoglegoogle

字符类快捷符号

符号

含义

等价表达式

\d

任意数字

[0-9]

\D

非数字

[^0-9]

\w

单词字符(字母、数字、下划线)

[a-zA-Z0-9_]

\W

非单词字符

[^a-zA-Z0-9_]

\s

空白字符(空格、制表符、换行等)

[\t\n\r\f\s]

\S

非空白字符

[^\t\n\r\f\s]

位置锚点符号

符号

含义

示例

^

字符串开始位置

^Java 匹配以 "Java" 开头的字符串

$

字符串结束位置

java$ 匹配以 "java" 结尾的字符串

\b

单词边界

\bcat\b 匹配单词 "cat" 而非 "category"

\B

非单词边界

\Bcat\B 匹配 "scatty" 中的 "cat"

特殊转义字符

由于其本身有特殊含义,需要前面加 \ 来表达其真实含义:

  • \. 匹配句点

  • \\ 匹配反斜杠

  • \^ 匹配脱字符

  • \$ 匹配美元符号

  • \? 匹配问号

  • \+ 匹配加号

  • \* 匹配星号

  • \( 匹配左括号

  • \) 匹配右括号

  • \[ 匹配左方括号

  • \] 匹配右方括号

  • \{ 匹配左花括号

  • \} 匹配右花括号

  • \| 匹配竖线

常用逻辑符号

符号

名称

含义

(?=X)

正向先行断言

匹配后面跟着 X 的位置

(?!X)

负向先行断言

匹配后面不跟着 X 的位置

(?<=X)

正向后行断言

匹配前面是 X 的位置

(?<!X)

负向后行断言

匹配前面不是 X 的位置

先行断言示例

// 匹配后面跟着"们"的汉字
Pattern.compile("\w(?=们)");
// 匹配后面不是数字的字母
Pattern.compile("[a-z]+(?!\d)");

贪婪与非贪婪匹配

默认情况下,*+?{n,m} 是贪婪的,会匹配尽可能多的字符。在其后添加 ? 可变为非贪婪(懒惰)模式。

贪婪模式

非贪婪模式

说明

*

*?

匹配尽可能少

+

+?

匹配尽可能少

?

??

匹配尽可能少

{n,m}

{n,m}?

匹配尽可能少

示例

String str = "<div>content</div><span>text</span>";
​
// 贪婪匹配:匹配整个字符串
Pattern greedy = Pattern.compile("<.*>");
Matcher m1 = greedy.matcher(str);
m1.find();  // 匹配 "<div>content</div><span>text</span>"
​
// 非贪婪匹配:只匹配第一个标签
Pattern lazy = Pattern.compile("<.*?>");
Matcher m2 = lazy.matcher(str);
m2.find();  // 匹配 "<div>"

常用的正则表达式

一、校验数字的表达式

描述

正则表达式

数字(整数)

^[0-9]*$

n位的数字

^\d{n}$

至少n位的数字

^\d{n,}$

m-n位的数字

^\d{m,n}$

零和非零开头的数字

^(0|[1-9][0-9]*)$

非零开头的最多带两位小数的数字

^([1-9][0-9]*)+(\.[0-9]{1,2})?$

带1-2位小数的正数或负数

^(\-)?\d+(\.\d{1,2})?$

正数、负数、和小数

^(\-|\+)?\d+(\.\d+)?$

有两位小数的正实数

^[0-9]+(\.[0-9]{2})?$

有1~3位小数的正实数

^[0-9]+(\.[0-9]{1,3})?$

非零的正整数

^[1-9]\d*$^\+?[1-9][0-9]*$

非零的负整数

^\-[1-9]\d*$

非负整数

^\d+$^[1-9]\d*|0$

非正整数

^-[1-9]\d*|0$

正浮点数

^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$

负浮点数

^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$

浮点数

^(-?\d+)(\.\d+)?$

二、校验字符的表达式

描述

正则表达式

汉字

^[\u4e00-\u9fa5]{0,}$

英文和数字

^[A-Za-z0-9]+$

长度为3-20的所有字符

^.{3,20}$

26个英文字母组成的字符串

^[A-Za-z]+$

26个大写英文字母组成的字符串

^[A-Z]+$

26个小写英文字母组成的字符串

^[a-z]+$

数字和26个英文字母组成的字符串

^[A-Za-z0-9]+$

数字、26个英文字母或下划线组成的字符串

^\w+$

中文、英文、数字包括下划线

^[\u4E00-\u9FA5A-Za-z0-9_]+$

三、特殊需求表达式

描述

正则表达式

Email地址

^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$

URL

[a-zA-z]+://[^\s]*

手机号码

^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$

身份证号(15位、18位)

^\d{15}|\d{18}$

密码(字母开头,6~18位,字母数字下划线)

^[a-zA-Z]\w{5,17}$

强密码(大小写字母+数字,8-10位)

^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$

日期格式(YYYY-MM-DD)

^\d{4}-\d{1,2}-\d{1,2}$

IP地址

((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))

腾讯QQ号

[1-9][0-9]{4,}

中国邮政编码

[1-9]\d{5}(?!\d)

中文字符

[\u4e00-\u9fa5]

双字节字符

[^\x00-\xff]

空白行

\n\s*\r

首尾空白字符

^\s*|\s*$

java.util.regex 包

java.util.regex 包是 Java 标准库中用于支持正则表达式操作的包,主要包含以下三个类:

  • Pattern 类:正则表达式的编译表示。没有公共构造方法,通过静态方法 compile() 创建实例

  • Matcher 类:对输入字符串进行解释和匹配操作的引擎。通过 Pattern 对象的 matcher() 方法获得

  • PatternSyntaxException:非强制异常类,表示正则表达式模式中的语法错误

Pattern 类常用方法

// 编译正则表达式
Pattern pattern = Pattern.compile("\\w+");
​
// 返回正则表达式的字符串形式
String regex = pattern.pattern();  // 返回 "\w+"
​
// 分隔字符串,返回 String[]
Pattern p = Pattern.compile("\\d+");
String[] str = p.split("我的姓名是:456456我的电话是:0532214");
// 结果:str[0]="我的姓名是:" str[1]="我的电话是:"
​
// 快捷匹配(一次性使用)
boolean isMatch = Pattern.matches("\\d+", "12345");  // 返回 true
​
// 忽略大小写
Pattern p2 = Pattern.compile("java", Pattern.CASE_INSENSITIVE);
​
// 多行模式(^和$匹配每行的开头和结尾)
Pattern p3 = Pattern.compile("^\\d+", Pattern.MULTILINE);
​
// 点号匹配所有字符(包括换行符)
Pattern p4 = Pattern.compile(".*", Pattern.DOTALL);

Pattern 常用标志常量

常量

说明

Pattern.CASE_INSENSITIVE

启用不区分大小写的匹配

Pattern.MULTILINE

启用多行模式(^和$匹配每行)

Pattern.DOTALL

点号匹配任意字符(包括换行符)

Pattern.UNICODE_CASE

启用 Unicode 感知的大小写折叠

Pattern.COMMENTS

允许空格和注释(忽略空白和#后的内容)

Matcher 类常用方法

Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher("There are 123 apples and 456 oranges.");
​
// find() - 查找下一个匹配子串
while (matcher.find()) {
    System.out.println(matcher.group());  // 输出 123 和 456
}
​
// matches() - 整个字符串是否完全匹配
boolean fullMatch = matcher.matches();
​
// lookingAt() - 字符串开头是否匹配
boolean startMatch = matcher.lookingAt();
​
// group() - 获取匹配的子字符串
System.out.println(matcher.group());     // 整个匹配
System.out.println(matcher.group(1));    // 第一个捕获组
​
// start() / end() - 获取匹配的起始和结束索引
int start = matcher.start();
int end = matcher.end();
​
// reset() - 重置匹配器
matcher.reset();  // 重置到字符串开头
matcher.reset("new input string");
​
// replaceAll() / replaceFirst() - 替换
String result1 = matcher.replaceAll("#");   // 替换所有
String result2 = matcher.replaceFirst("#"); // 替换第一个
​
// appendReplacement() / appendTail() - 高级替换
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
    matcher.appendReplacement(sb, "数字");
}
matcher.appendTail(sb);

捕获组的使用

捕获组使用圆括号 () 定义,按左括号出现的顺序从 1 开始编号,group(0) 表示整个匹配。

// 基本捕获组
Pattern pattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher matcher = pattern.matcher("2025-08-15");
if (matcher.matches()) {
    System.out.println("Year: " + matcher.group(1));   // 2025
    System.out.println("Month: " + matcher.group(2));  // 08
    System.out.println("Day: " + matcher.group(3));    // 15
}
​
// 命名捕获组(Java 7+)
Pattern namedPattern = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})");
Matcher namedMatcher = namedPattern.matcher("2025-08-15");
if (namedMatcher.matches()) {
    System.out.println("Year: " + namedMatcher.group("year"));
}
​
// 非捕获组 (?:...) - 不保存匹配结果
Pattern nonCapture = Pattern.compile("a(?:bc)*d");
Matcher ncm = nonCapture.matcher("abcbcbcd");
System.out.println(ncm.matches());  // true
​
// 捕获组的数量
int groupCount = matcher.groupCount();

完整示例代码

import java.util.regex.*;
​
public class RegexExample {
    public static void main(String[] args) {
        // 示例1:基本匹配
        String content = "I am alan";
        String pattern = ".*alan.*";
        boolean isMatch = Pattern.matches(pattern, content);
        System.out.println("是否包含 'alan'? " + isMatch);
        
        // 示例2:提取数字
        Pattern numPattern = Pattern.compile("\\d+");
        Matcher numMatcher = numPattern.matcher("订单号: 12345, 金额: 678.90");
        while (numMatcher.find()) {
            System.out.println("找到数字: " + numMatcher.group());
        }
        
        // 示例3:验证邮箱
        String email = "user@example.com";
        String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
        boolean isValidEmail = Pattern.matches(emailRegex, email);
        System.out.println("邮箱有效: " + isValidEmail);
        
        // 示例4:替换操作
        Pattern replacePattern = Pattern.compile("\\d+");
        Matcher replaceMatcher = replacePattern.matcher("有 100 个苹果和 200 个橘子");
        String result = replaceMatcher.replaceAll("#");
        System.out.println(result);  // 输出 "有 # 个苹果和 # 个橘子"
        
        // 示例5:分割字符串
        Pattern splitPattern = Pattern.compile("[,;\\s]+");
        String[] items = splitPattern.split("apple, banana;orange grape");
        for (String item : items) {
            System.out.println(item);
        }
        
        // 示例6:使用正则表达式验证手机号
        String phone = "13812345678";
        String phoneRegex = "^(13[0-9]|14[5|7]|15[0-9]|18[0-9])\\d{8}$";
        System.out.println("手机号有效: " + phone.matches(phoneRegex));
    }
}

常见的 PatternSyntaxException 原因

错误原因

示例

正确写法

未转义特殊字符

(abc

\\(abc

不完整的字符类

[a-z

[a-z]

无效的重复次数

a{1,0}

a{0,1}a?

未闭合的捕获组

(abc

(abc)

无效的转义序列

\i

使用有效的转义如 \d

性能优化建议

  1. 预编译正则表达式:重复使用的正则表达式应编译为 Pattern 对象

    // 不推荐:每次调用都重新编译
    public boolean isValid(String input) {
        return Pattern.matches("^\\d+$", input);
    }
    ​
    // 推荐:预编译为 static final 常量
    private static final Pattern NUMBER_PATTERN = Pattern.compile("^\\d+$");
    public boolean isValid(String input) {
        return NUMBER_PATTERN.matcher(input).matches();
    }
  2. 避免灾难性回溯:避免使用嵌套的量词,如 (a+)*(a*)*

  3. 使用 possessive 量词(Java 支持):++*+?+{n,m}+,防止回溯

    // 贪婪量词可能导致大量回溯
    Pattern bad = Pattern.compile("(a|b)*b");
    ​
    // possessive 量词不回溯,性能更好
    Pattern good = Pattern.compile("(a|b)*+b");
  4. 明确使用边界:使用 ^$ 减少匹配范围

评论