【Java】Pattern 与 Matcher 类的常见应用

在 Java 编程中,我们经常需要处理字符串的匹配和替换操作。为了便捷地实现这些功能,Java 提供了 Pattern 类和 Matcher 类。Pattern 类用于定义正则表达式模式,而 Matcher 类用于在给定的输入字符串中进行匹配操作。

本文将介绍 Pattern 类和 Matcher 类在实际场景中常见的应用,以及列举一些常见的正则表达式示例。

基本定义

Java 正则表达式通过 java.util.regex 包下的 Pattern 类与 Matcher 类实现

Pattern 类的实例适应正则表达式的编译表示形式,指定为字符串的正则表达式必须首先被编译为此类的实例。然后,可将得到的模式用于创建 Matcher 对象,根据正则表达式,该对象可以与任意字符序列匹配。执行匹配所涉及的所有状态都会保留在匹配器中,因此多个匹配器可以共享同一模式。

因此,典型的调用顺序是:

  1. Pattern.compile() 生成编译后的正则表达式
  2. matcher() 生成匹配器
  3. matches() 执行匹配操作,并记录下匹配结果状态

单独用 Pattern 只能使用 Pattern.matches(String regex,CharSequence input) 这一种最基础最简单的匹配。

这种匹配的模式,只能满足一次性使用,不能重复使用,因此它的效率不高。但是从另一个角度来看,此类的实例是不可变的,可供多个并发线程安全使用。相比而言,Matcher 类的实例在并发下则没那么安全了。

Pattern 类

Pattern 类是 java.util.regex 包中的一个类,它用于定义正则表达式模式。正则表达式是一种强大的文本匹配工具,它可以用于在字符串中查找、替换或提取特定模式的文本。

Pattern 类用于创建一个正则表达式,也可以说创建一个匹配模式。它的构造方法是私有的,不可以直接创建,但可以通过静态方法 Pattern.complie(String regex) 这个简单工厂方法编译一个正则表达式模式。编译后的 Pattern 对象可以用于创建 Matcher 对象,后者用于在输入字符串中执行匹配操作。

java 复制代码
Pattern p = Pattern.compile("\\w+");

如果直接使用 matches 静态方法,则对应返回一个 boolean 结果,表示是否匹配上目标表达式:

java 复制代码
Pattern.matches("\\d+", "154234");

Matcher 类

Matcher类是Pattern类的内部类,用于在给定的输入字符串中执行匹配操作。Matcher对象可以通过调用Pattern对象的 matcher() 方法来创建,然后可以使用各种方法执行匹配操作。

java 复制代码
Matcher m = Pattern.compile("\\w+").matcher("");

得到 Matcher 实例后,可以通过一系列的方法进行操作:

  • find():判断是否有下一组匹配成功的结果
  • lookingAt():起始位置是否匹配成功
  • group():等价于 group(0),返回匹配整个表达式的结果
  • start():等价于 start(0),返回整个结果的起始下标(包含)
  • end():等价于 end(0),返回整个结果的末尾下标(不包含)

下面给出一段实例代码:

java 复制代码
@Test
public void testMatchSinglePattern() {
    String s1 = "vnkjslifgoia123aoivcmaiosdjfaio456vmldfjviodf789avoasdmfoi010";
    String s2 = "000vnkjslifgoia123aoivcmaiosdjfaio456vmldfjviodf789avoasdmfoi010";

    Pattern p = Pattern.compile("\\d+");
    Matcher m1 = p.matcher(s1);
    Matcher m2 = p.matcher(s2);

    // 包含字母,全匹配失败
    Assert.assertFalse(m1.matches());
    Assert.assertFalse(m2.matches());

    // 存在数字,有匹配的部分
    Assert.assertTrue(m2.find());
    // 开头部分是否匹配成功
    Assert.assertFalse(m1.lookingAt());
    Assert.assertTrue(m2.lookingAt());

    // 分别取出匹配的部分,每执行一次find()方法,获取一次下一个匹配项的信息
    if (m1.find()) {
        Assert.assertEquals(m1.group(), "123");
        Assert.assertEquals(m1.start(), 12);
        Assert.assertEquals(m1.end(), 15);
    }
    if (m1.find()) {
        Assert.assertEquals(m1.group(), "456");
        Assert.assertEquals(m1.start(), 31);
        Assert.assertEquals(m1.end(), 34);
    }
}

捕获组 group()

当匹配的正则表达式模式通过括号 () 分组创建了多个匹配单元时,成功匹配的结果则成为捕获组。对于一个 Matcher 对象匹配字符串 s 的结果来说, group()group(0) 对应匹配整个表达式,m.group() 的结果与 s.substring(m.start(), m.end()) 的结果是等价的。

通过 groupCount() 可以返回当前捕获组的数量,可以通过 group(1)group(2) 依次取出每个子单元匹配的内容。

start()end() 方法,则对应获取匹配内容的起始下标和末尾下标(与字符串一样,遵循"包头不包尾"原则)。类似的,start(1)end(1)start(2)end(2) 则是获取每个单元捕获组的首尾下标

下面给出一个例子来进行说明:

java 复制代码
@Test
public void testMatchMultiPattern() {
    String s = "aaa111AAA bbb222 cccccc33CCCCCC  ";

    // 两组匹配内容,用括号()封装
    Pattern p = Pattern.compile("([a-z]+)(\\d+)([A-Z]+)");
    Matcher m = p.matcher(s);

    if (m.find()) {
        // 匹配模式中有三个()子序列,因此捕获组内count为3
        Assert.assertEquals(m.group(), "aaa111AAA");
        Assert.assertEquals(m.group(0), "aaa111AAA");
        Assert.assertEquals(m.groupCount(), 3);

        Assert.assertEquals(m.group(1), "aaa");
        Assert.assertEquals(m.group(2), "111");
        Assert.assertEquals(m.group(3), "AAA");

        Assert.assertEquals(m.start(), 0);
        Assert.assertEquals(m.start(1), 0);
        Assert.assertEquals(m.start(2), 3);
        Assert.assertEquals(m.start(3), 6);

        Assert.assertEquals(m.end(), 9);
        Assert.assertEquals(m.end(1), 3);
        Assert.assertEquals(m.end(2), 6);
        Assert.assertEquals(m.end(3), 9);
    }

    if (m.find()) {
        Assert.assertEquals(m.group(), "cccccc33CCCCCC");
        Assert.assertEquals(m.group(0), "cccccc33CCCCCC");
        Assert.assertEquals(m.groupCount(), 3);

        Assert.assertEquals(m.group(1), "cccccc");
        Assert.assertEquals(m.group(2), "33");
        Assert.assertEquals(m.group(3), "CCCCCC");

        Assert.assertEquals(m.start(), 17);
        Assert.assertEquals(m.start(1), 17);
        Assert.assertEquals(m.start(2), 23);
        Assert.assertEquals(m.start(3), 25);

        Assert.assertEquals(m.end(), 31);
        Assert.assertEquals(m.end(1), 23);
        Assert.assertEquals(m.end(2), 25);
        Assert.assertEquals(m.end(3), 31);
    }
}

范围匹配 region

region 用于设定查找范围,设定之后之前查找保存在 mathcer 中的结果清空,随后调用 find() 可在指定的下标范围中进行匹配。

java 复制代码
@Test
public void testMatcherRegion() {
    String s = "aaa111AAACCCCCC  ";

    Pattern p = Pattern.compile("\\d+");
    Matcher m = p.matcher(s);
    m.region(0, 3);

    Assert.assertFalse(m.find());

    m.region(4, 10);
    m.find();
    Assert.assertEquals(m.group(), "11");
}

重置匹配 reset

顾名思义,reset 重置 matcher 实例中的匹配结果,重置后匹配记录被清空,需要再次调用 find() 方法重新进行匹配。

java 复制代码
@Test
public void testMatcherReset() {
    String s = "aaa111AAA bbb222 cccccc33CCCCCC  ";

    // 两组匹配内容,用括号()封装
    Pattern p = Pattern.compile("[a-z]+");
    Matcher m = p.matcher(s);

    if (m.find()) {
        Assert.assertEquals(m.group(), "aaa");
    }
    if (m.find()) {
        Assert.assertEquals(m.group(), "bbb");
    }
    m.reset();
    if (m.find()) {
        Assert.assertEquals(m.group(), "aaa");
    }
}

其他常用的字符串操作

字符串分割 split

java 复制代码
@Test
public void testStringSplit() {
    String s = "Good morning, ladies and gentleman!";

    // 按空格分割字符串,获取各个单词
    Pattern spacePattern = Pattern.compile("\\s+");

    String[] arr = spacePattern.split(s);
    Assert.assertEquals(arr[0], "Good");
    Assert.assertEquals(arr[1], "morning,");
    Assert.assertEquals(arr[2], "ladies");
    Assert.assertEquals(arr[3], "and");
    Assert.assertEquals(arr[4], "gentleman!");
}

字符串替换 replace

java 复制代码
@Test
public void testStringReplacement() {
    String s = "apple-red,banana-yellow,grass-green,sky-blue";

    Pattern p = Pattern.compile("-");
    Assert.assertEquals(p.matcher(s).replaceAll(":"), "apple:red,banana:yellow,grass:green,sky:blue");
    Assert.assertEquals(p.matcher(s).replaceFirst("->"), "apple->red,banana-yellow,grass-green,sky-blue");
}

字符串匹配拼接 append

主要包括 appendReplacement()appendTail() ,两个方法执行的动作,简单来说:

  • appendReplacement() 将本次查找匹配到的字符串及其之前的字符追加到一个 StringBuffer 中去。
  • appendTail() 是将匹配后面剩下的未匹配的字符串追加到一个 StringBuffer 中。
java 复制代码
@Test
public void testStringBuilderAppend(){
    Pattern pattern = Pattern.compile("a|b");
    Matcher matcher = pattern.matcher("aiiiibqqqqqafffffff@here.com");

    StringBuffer sb1 = new StringBuffer();
    StringBuffer sb2 = new StringBuffer();

    if (matcher.find()) {
        matcher.appendReplacement(sb1, matcher.group());
        Assert.assertEquals(sb1.toString(), "a");
    }
    if (matcher.find()) {
        matcher.appendReplacement(sb1, matcher.group());
        Assert.assertEquals(sb1.toString(), "aiiiib");
    }
    if (matcher.find()) {
        matcher.appendReplacement(sb1, matcher.group());
        Assert.assertEquals(sb1.toString(), "aiiiibqqqqqa");
    }

    matcher.appendTail(sb2);
    Assert.assertEquals(sb2.toString(), "fffffff@here.com");
}

参考文档

相关推荐
公贵买其鹿11 分钟前
List深拷贝后,数据还是被串改
java
xlsw_3 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹4 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭5 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫5 小时前
泛型(2)
java
超爱吃士力架5 小时前
邀请逻辑
java·linux·后端
南宫生5 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石5 小时前
12/21java基础
java
李小白665 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp5 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea