如何使用java进行字符串的模糊匹配

1.场景

我们经常会有一些需要在java中做字符串的模拟匹配的场景。最常见的比如有:

  • 数据库分表名称匹配。
    比如我们的分表名称为“t_user_1”,”t_user_2”,”t_user_3”。我们需要对这一类分表进行匹配。我们预期的写法就是”t_user_*“,无论后面的数字如何变化,我们都能匹配得到。

  • 网关对url进行匹配,做鉴权例外。
    在很多情况下我们在做网关鉴权时,需要对一些url进行排除。比如需要排除”/pay/wx/callback”,”pay/alipay/callback”等url。因为未来可能还会增加多种支付方式,因此排除例外时,我们希望只要是”/pay”目录下的所有接口都不再健全。于是我们的预期写法是”/pay/**”。

2.实现方法

2.1 正则匹配

这几乎是所有人都会想到的匹配方式,但是正则匹配总会给人一种”太重“的感觉。尤其是用在一些基础中间件上,只要是慢一点,效果都会被放大很多倍。

1
2
3
4
5
6
7
StringBuffer pattern  = new StringBuffer();
pattern.append("^");
pattern.append(exceptUrl.replaceAll("\\*", ".*"));
pattern.append("$");
Pattern r = Pattern.compile(pattern.toString());
Matcher m = r.matcher(requestUri);
return m.matches();

2.2 Apache Ant 匹配规则

ant匹配规则

Spring对这套匹配规则已经有了具体的实现AntPathMatcher,使用上也非常的简单。

1
2
AntPathMatcher antPathMatcher = new AntPathMatcher();
return antPathMatcher.match(exceptUrl,requestUri);

3.性能对比

我们生成100000个url,与指定的url进行匹配,看谁的速度最快。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

/**
* AntPathMatcher 正则表达式测试
*/
@Test
public void antSpeedTest(){
for(int i =0;i<100000;i++){
urls.add("/"+i);
}
System.out.println("urls生成完毕");

long testTime=System.currentTimeMillis();
urls.forEach(u->{
antMatch(u);
});
System.out.println("AntPathMatcher 耗时:"+(System.currentTimeMillis()-testTime));

}

public void antMatch(String url){
String[] excludeArray =exclude.trim().split(",");
AntPathMatcher antPathMatcher = new AntPathMatcher();
Arrays.stream(excludeArray)
.anyMatch(e->
{
return antPathMatcher.match(e,url);
});
}


/**
* 正则表达式测试
*/
@Test
public void regularSpeedTest(){
for(int i =0;i<100000;i++){
urls.add("/"+i);
}
System.out.println("urls生成完毕");

long testTime=System.currentTimeMillis();
urls.forEach(u->{
regularMatch(u);
});
System.out.println("正则表达式 耗时:"+(System.currentTimeMillis()-testTime));

}
public void regularMatch(String requestUri){
String[] excludeArray =exclude.trim().split(",");
Arrays.stream(excludeArray)
.anyMatch(e->
{
if(StringUtils.isNotBlank(e)&&e.contains("*"))
{
//通配符匹配
StringBuffer pattern = new StringBuffer();
pattern.append("^");
pattern.append(e.replaceAll("\\*", ".*"));
pattern.append("$");
Pattern r = Pattern.compile(pattern.toString());
Matcher m = r.matcher(requestUri);
return m.matches();
}
else
{
return e.equals(requestUri);
}
});
}

4.结果

  • 正则表达式 耗时:2549
  • AntPathMatcher 耗时:1478

可以看出,antPathMatcher的性能是更好的。