从源码分析正则表达式的实现原理

目前很多语言都支持正则表达式,正则表达式在文本处理可谓是一神器,而正则表达式都是一些符号组成,对新手有种望文却步的感觉。本文通过分析 Java 正则表达式的底层实现,以减少大家对正则表达式的恐惧。


java.util.regex 包主要包括以下三个类 Pattern类、Matcher类和PatternSyntaxException类。

Pattern类:
一般我们使用该对象调用其静态方法就能返回 Pattern 对象,该对象是一个正则表达式对象
Matcher类:
一般我们调用 Pattern 对象的 matcher() 方法来获得一个 Matcher 对象。
PatternSyntaxException类:
这是正则表达式的异常类。在匹配错误时会报一些异常。

我们先来看一下一段代码:

    //输入的字符串
    String inputString = "abc123def456";
    //匹配的规则
    String regex = "\\d\\d\\d";
    //根据规则,创建正则表达式对象
    Pattern pattern = Pattern.compile(regex);
    //创建匹配器
    Matcher matcher = pattern.matcher(inputString);
    //开始匹配
    while (matcher.find()) {
    
    
        System.out.println("找到了" + matcher.group(0));
    }
运行结果


Matcher 类定义了如下基本类型:

    //一个数组。用来存找到的子串的索引,默认容量是 20(因为有匹配规则可以分组,要多点容量,满了则扩容)
    int[] groups;

    /*最后匹配模式的字符串范围。若最后一次匹配失败,则 first=-1;last 最初是 0,
     *他表示最后一个匹配的结尾的索引(这是下一次搜索开始的地方)。
     */
    int first = -1, last = 0;

    //上次匹配操作中匹配的内容的结束索引。
    int oldLast = -1;


 当调用 matcher 对象的find() 方法时,会根据匹配的规则,找到字符串的子串,在上面的例子比如就是先找到 “123” 的开始索引,即"3",和结束索引加一,即 “6”,放到数组 groups[] 中,即有 groups[0]=3,groups[1]=6
 同时 first=3,last=6,oldLast=6,下次调用 find() 方法就会从索引 “6” 开始匹配。

//这是matcher.find()的底层实现
    public boolean find() {
    
    
        //每次寻找子串都将last赋给 nextSearchIndex,以他为索引起点开始寻找
        int nextSearchIndex = last;
        //如果第一次从字符串开始找,则索引加加
        if (nextSearchIndex == first)
            nextSearchIndex++;

        //如果下一次寻找的起点在 from 之前,即 nextSearchIndex<0,则重新等于 from 开始
        if (nextSearchIndex < from)
            nextSearchIndex = from;

        //如果下一次寻找的起点超过 to ,即已遍历完字符串,则 return false
        if (nextSearchIndex > to) {
    
    
            for (int i = 0; i < groups.length; i++)
                groups[i] = -1;
            return false;
        }
        //从 nextSearchIndex 开始尝试匹配满足条件的子字符串
        return search(nextSearchIndex);
    }
//我们再进去 find() 方法里面调用的 search() 方法
    boolean search(int from) {
    
    
        this.hitEnd = false;
        this.requireEnd = false;
        from        = from < 0 ? 0 : from;
        this.first  = from;
        this.oldLast = oldLast < 0 ? from : oldLast;
        for (int i = 0; i < groups.length; i++)
            groups[i] = -1;
        acceptMode = NOANCHOR;
        //主要是调用该 Node 的这个类的 match() 方法,在下面。为的就是根据 first 和 last 索引给到 groups[0]=3,groups[1]=6
        boolean result = parentPattern.root.match(this, from, text);
        if (!result)
            this.first = -1;
        this.oldLast = this.last;
        return result;
    }

    boolean match(Matcher matcher, int i, CharSequence seq) {
    
    
            matcher.last = i;
            matcher.groups[0] = matcher.first;
            matcher.groups[1] = matcher.last;
            return true;
        }
debug 过程1

 接下来调用 group() 方法会根据 groups[0]=3,groups[1]=6 记下的索引,从而将字符串截取范围 [3,6) 返回。

    //我们看到 group() 的方法
    public String group(int group) {
    
    
        //如果 first<0,即没有匹配到子串,抛异常
        if (first < 0)
            throw new IllegalStateException("No match found");
        //如果 group<0,或者在该规则匹配字串共有 n 组的前提下,而 group>n ,则抛异常
        if (group < 0 || group > groupCount())
            throw new IndexOutOfBoundsException("No group " + group);
        //如果在调用 find() 方法时,没有找到字串,则返回 null
        if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
            return null;
        //调用该方法去按照 groups 数组存的索引,来进行截取原字符串,左闭右开
        return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
    }

 第二次重新匹配时,groups[0],groups[1] 会变回 -1,以 last 索引重新开始寻找满足的子串。

debug 过程2
debug 过程3

当匹配规则有分组时,比如对上面的代码稍微改一下匹配规则:

    //输入的字符串
    String inputString = "abc123def456";
    //匹配的规则
    String regex = "(\\d\\d)(\\d)";
    //根据规则,创建正则表达式对象
    Pattern pattern = Pattern.compile(regex);
    //创建匹配器
    Matcher matcher = pattern.matcher(inputString);
    //开始匹配
    while (matcher.find()) {
    
    
        System.out.println("找到了:" + matcher.group(0));
        System.out.println("找到组1:" + matcher.group(1));
        System.out.println("找到组2:" + matcher.group(2));
    }
运行结果

此时依然会有 groups[0]=3,groups[1]=6
并且记录 第一组子串的索引 “3,5” 为 groups[2]=3,groups[3]=5
第二组子串的索引 “5,6” 为 groups[4]=5,groups[5]=6
当然了,分组越多,groups[···],以此类推。

debug 过程1
debug 过程2

等到遍历完字符串,找不到满足规则的情况时,全部复原。

debug 过程3

over!!!

猜你喜欢

转载自blog.csdn.net/lhrfighting/article/details/118561633