目前很多语言都支持正则表达式,正则表达式在文本处理可谓是一神器,而正则表达式都是一些符号组成,对新手有种望文却步的感觉。本文通过分析 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;
}
接下来调用 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 索引重新开始寻找满足的子串。
当匹配规则有分组时,比如对上面的代码稍微改一下匹配规则:
//输入的字符串
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[···],以此类推。
等到遍历完字符串,找不到满足规则的情况时,全部复原。
over!!!