前言

最近一直在修复代码安全方面的问题,之前已经写了两篇文章分享给大家了。

其实我还遇到过一个正则表达式回溯问题。

有时候我们在日常工作中,喜欢用正在表达式查找指定字符串,或者替换指定字符串,比写一堆Java代码,更方便和快捷。

但我想说的是,如果正则表达式写的不好,程序在匹配指定字符串时,如果产生了大量的回溯,导致程序性能会非常差。

1 案发现场

最近我用sonar扫描代码仓库中的代码问题时,发现了一个非常有意思的问题。

当时sonar指出这正则表达式有问题:

\\d+\\.+\\d+

还有这段代码也有问题:

public String getValue(String value) {
   if(value.contains(".")) {
     return value.replaceAll("0+?$","").replaceAll("[.]$","");
   }
}

第一个正则表达式是为了从字符串中获取数字,比如:123.5m,获取后的值为123.5。

第二个和第三个正则表达式,是为了将字符串中.0去掉,比如:12.001.0,变成121。

这三个正在表达式,从功能的角度来说是没有问题的,能够正常匹配字符串。

但sonar在扫描这几个正则表达式之后,却出现了正则表达式回溯问题的提示,这种正则表达式写的不好,非常影响性能。

那么,什么是正则表达式回溯问题呢?

2 什么是回溯问题?

2.1 什么是回溯问题?

比如有字符串“abbc”,正则表达式为/ab{1,3}c/,因为默认是贪婪模式。

所以会尽量匹配多一点内容,也就是想尽量匹配多个b,于是步骤就是如下:

  1. a -> a
  2. ab -> ab
  3. abb -> abb
  4. abbb -> abbc
  5. abb -> abb
  6. abbc -> abbc

如上,第4步因为想要尽可能的匹配多一点内容,所以想要匹配3个b,但是没匹配上,匹配到了c,于是就回溯。

然后匹配下一个正则表达式,也就是c,然后c与c匹配,成功返回。

这就是正则表达式的回溯问题,当需要匹配的是一个超长字符串,并且发生了回溯问题,那其计算量能够想象有多大了吧。

2.2 如何解决回溯问题呢?

2.2.1 使用懒惰模式

由于是因为贪婪模式导致的,那只要把贪婪模式改为懒惰模式即可,只要后面加个?就行,改为/ab{1,3}?c/。

但是懒惰模式也无法完全回溯问题,比如:字符串“abbc”,正则表达式/ab{1,3}?c/,则步骤如下:

  1. a -> a
  2. ab -> ab
  3. abc -> abb
  4. ab -> ab
  5. abb -> abb
  6. abbc -> abbc 如上,第3步因为想要尽可能少匹配一点内容,于是就只匹配一个b,但没匹配上,于是就回溯,

与贪婪模式相反,贪婪模式是字符串回溯,而懒惰模式是正则表达式回溯。

2.2.2 使用独占模式

使用独占模式,只要后面加个+就行,改为/ab{1,3}+c/。

因为独占模式是只要不匹配就结束,不会进行回溯,但是也不能完全解决回溯问题,因为其也是会尽可能的匹配多一点内容,所以也会导致回溯问题。

总的来说,懒惰模式和独占模式都是只能减少回溯问题,但是不能完全避免回溯问题。

可优化方案:

  1. 少用贪婪模式,多用独占模式。
  2. 减少分支选择,比如(X|Y|Z)这种分支选择,如要只用分支,可以将常用的选项放在前面,可以提取公共因子,比如(aabb|aacc|aadd)可以换成aa(bb|cc|dd),可以用三次匹配来代替(X|Y|Z)。
  3. 减少捕获嵌套,就是一个()代表一个捕获组,比如/a(bc)de(fgh)ijk(lmn)/,然后\1代表(bc),\2代表(fgh),\3代表(lmn), 非捕获组就是(?:X),比如/a(?:bc)de(fgh)ijk(?:lmn)/,则\1代表(fgh),或者直接不用括号/abcde(fgh)ijklmn/;
  4. 能不用正则表达式就不用正则表达式。

3 如何优化代码?

知道正则表达式回溯问题,会非常影响正则表达式匹配字符串的性能。

那么,如何优化上面的代码呢?

将正则表达式:\d+\.+\d+,改成\d+\.?\d*+。

然后将将字符串中.0的逻辑,改成不用正则表达式匹配。

其实在Java中已经提供了实现这类功能的方法,我们可以使用BigDeciaml类。

代码优化如下:

public String getValue(String value) {
   if(value.contains(".")) {
     return new BigDeciaml(value).strpTrailingZeros().toPlainString();
   }
}

使用了BigDeciaml类的strpTrailingZeros和toPlainString方法。

这两个方法的底层不是用正则表达处理的。

image最后给大家推荐一个非常有用的网站,上面列举了我们日常工作中经常会用到正则表达式,可以直接测试和使用: https://c.runoob.com/front-end/854/

image

希望对你会有所帮助。

此外,加入了星球还没加微信群的小伙伴,可以加一下我的微信:li_su123,回复:知识星球,我拉你进群。星球的微信群经常会有发红包和送书的活动。

最后修改:2026 年 06 月 06 日
如果觉得我的文章对你有用,请随意赞赏