正则表达式底层实现

巡夜 / 2024-01-30 / 原文

1、不考虑分组

代码示例

public static void main(String args[]){
    String content = "2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布,几周后其获得了Apple公司Mac OS X的工业标准的支持。" +
            "2001年9月24日,J2EE1.3发布。2002年2月26日,J2SE1.4发布。自此Java的计算能力有了大幅提升,与J2SE1.3相比," +
            "其多了近62%的类和接口。在这些新特性当中,还提供了广泛的XML支持、安全套接字(Socket)支持(通过SSL与TLS协议)、" +
            "全新的I/OAPI、正则表达式、日志与断言。2004年9月30日,J2SE1.5发布,成为Java语言发展史上的又一里程碑。为了表示该版本的重要性," +
            "J2SE 1.5更名为Java SE 5.0(内部版本号1.5.0),代号为“Tiger”,Tiger包含了从1996年发布1.0版本以来的最重大的更新,其中包括泛型支持、" +
            "基本类型的自动装箱、改进的循环、枚举类型、格式化I/O及可变参数。";

    // \\d代表一个任意的数字,这段正则匹配的是一个四位的数字
    String regStr = "\\d\\d\\d\\d";
    // 创建模式对象,即正则表达式对象
    Pattern pattern = Pattern.compile(regStr);
	// 创建匹配器,按照正则表达式的规则匹配content字符串
    Matcher matcher = pattern.matcher(content);

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

代码解析

①、matcher.find()

  1. 根据指定的规则,定位满足规则的子字符串(比如content中的第一个匹配结果2000)

  2. 找到后,将子字符串的开始、结束索引记录到matcher对象的属性 int[] groups 中

    groups[0] = 0; 记录的为子字符串开始索引

    groups[1] = 4; 记录的为子字符串结束索引 + 1

  3. 同时记录oldLast的值为子字符串结束索引 + 1,下次执行matcher.find()时,就从oldLast开始匹配

②、matcher.group(0)

// 源码
public String group(int group) {
    if (first < 0)
        throw new IllegalStateException("No match found");
    if (group < 0 || group > groupCount())
        throw new IndexOutOfBoundsException("No group " + group);
    if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
        return null;
    return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
}

根据groups[0] = 0 和 groups[1] = 4的记录的位置,从content开始截取字符串返回,就是[0,4),左闭右合,包含索引为0的值,但不包含索引为4的值

2、考虑分组

什么是分组,比如 (\d\d)(\d\d),正则表达式中有(),表示分组,第1个()表示第1组,第2个()表示第2组

代码示例

public static void main(String args[]){
    String content = "2000年5月,JDK1.3、JDK1.4和J2SE1.3相继发布,几周后其获得了Apple公司Mac OS X的工业标准的支持。" +
            "2001年9月24日,J2EE1.3发布。2002年2月26日,J2SE1.4发布。自此Java的计算能力有了大幅提升,与J2SE1.3相比," +
            "其多了近62%的类和接口。在这些新特性当中,还提供了广泛的XML支持、安全套接字(Socket)支持(通过SSL与TLS协议)、" +
            "全新的I/OAPI、正则表达式、日志与断言。2004年9月30日,J2SE1.5发布,成为Java语言发展史上的又一里程碑。为了表示该版本的重要性," +
            "J2SE 1.5更名为Java SE 5.0(内部版本号1.5.0),代号为“Tiger”,Tiger包含了从1996年发布1.0版本以来的最重大的更新,其中包括泛型支持、" +
            "基本类型的自动装箱、改进的循环、枚举类型、格式化I/O及可变参数。";

    String regStr = "(\\d\\d)(\\d\\d)";
    Pattern pattern = Pattern.compile(regStr);

    Matcher matcher = pattern.matcher(content);

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

代码解析

①、matcher.find()

  1. 根据指定的规则,定位满足规则的子字符串(比如content中的第一个匹配结果2000)

  2. 找到后,将子字符串的开始、结束索引记录到matcher对象的属性 int[] groups 中

    1. groups[0] = 0; 记录的为子字符串开始索引 groups[1] = 4; 记录的为子字符串结束索引 + 1
    2. 记录第1组()匹配到的字符串groups[2] = 0; groups[3] = 2;
    3. 记录第2组()匹配到的字符串groups[4] = 2; groups[5] = 4;
    4. 更多的分组......
  3. 同时记录oldLast的值为子字符串结束索引 + 1,下次执行matcher.find()时,就从oldLast开始匹配

②、matcher.group()

  1. group(0):表示匹配到的子字符串
  2. group(1):表示匹配到的子字符串的第1组子串
  3. group(2):表示匹配到的子字符串的第2组子串
  4. 更多的分组......