2

最近我在代码中发现了以下正则表达式。由于它正在检查的字符串非常大,它冻结了浏览器。运行一些实验,我使用以下代码测量了时间

var s = 'Lorem ipsum dolor sit amet, rhoncus nam sem feugiat, vel vel, viverra ultrices interdum. Volutpat ac congue. Lacinia sit donec quis facilisi, magna cubilia volutpat lectus fusce ligula quis, sit sed vivamus eget mauris quisque, aenean aenean nec litora litora massa, malesuada turpis pretium. Magnis metus nulla mauris dictum ligula, odio facilisis nullam laoreet. Aliquam tincidunt enim sit dolor mi. Duis malesuada pede, tortor consectetuer facilisis massa et leo vel. Eget fames tellus mi. Suscipit tincidunt fusce lacus convallis, ornare eu sed eu gravida interdum. Vivamus ipsum, maecenas penatibus, lacus posuere, eu cum, mauris ea libero elit. Libero blandit mattis mi sapien, iaculis wisi sit convallis, est in libero, elementum cras in a cum a vestibulum';

for (var i = 0; i < 3; i++) {
  s += s;
}

start = new Date().getTime();
s.match(/AAA/i)
stop = new Date().getTime();
console.log("AAA took " + (stop - start) + " ms")

start = new Date().getTime();
s.match(/BBB/i)
stop = new Date().getTime();
console.log("BBB took " + (stop - start) + " ms")

start = new Date().getTime();
s.match(/CCC/i)
stop = new Date().getTime();
console.log("CCC took " + (stop - start) + " ms")

start = new Date().getTime();
s.match(/.*(AAA|BBB|CCC).*/i)
stop = new Date().getTime();
console.log("Combined took " + (stop - start) + " ms")

上面的打印

AAA took 0 ms
BBB took 1 ms
CCC took 0 ms
Combined took 53 ms

你能用简单的话解释一下为什么这个正则表达式这么慢,而检查各个部分几乎不需要时间吗?是否有另一种编写单行正则表达式来检查是否出现多个字符串的方法,这会更快地产生结果?

4

2 回答 2

1

, /AAA/i,不仅在交替捕获组的使用上有所不同,而且在/BBB/i模式/CCC/i上也有所不同。/.*(AAA|BBB|CCC).*/i.*

由于第一个是贪心点模式,该/.*(AAA|BBB|CCC).*/i模式会减慢匹配速度。.*它的工作方式如下:

  • 它抓取它可以匹配的所有字符(除换行符之外的 0 个或多个 chsrs),因此,基本上,它抓取整行
  • 然后它必须匹配AAAor BBBor CCC,因此回溯开始:引擎从匹配结束时产生一个 char 并尝试匹配其中一个选项
  • 如果缺少替代项,或者如果它们位于非常长的字符串的开头,则引擎将徒劳地尝试为捕获组匹配容纳字符串的一部分。

查看这个带有可视化回溯步骤的正则表达式调试器。

因此,查找行/字符串是否包含 3 种替代方法中的任何一种的最佳方法就是使用

/(?:AAA|BBB|CCC)/i

因为这个正则表达式可以找到部分匹配(它不需要像 Java 中的完整字符串匹配String#match())。

如果您需要在多行字符串中查找具有备选方案之一的行,最好逐行处理(例如,用 拆分\n)然后.filter(x => /(?:AAA|BBB|CCC)/i.test(x)).

注意(?:...)是一个不创建子匹配的非捕获组,并且由于引擎不必为捕获分配额外的内存,因此开销较小。

于 2018-05-03T12:42:57.060 回答
0

您没有.*在之前的测试中包含 ,导致您match很快返回 false 。

在这里,您可以看到即将30ms运行的每个测试。

全球还只需要40ms.

除此之外,使用s.match(/AAA|BBB|CCC/i)速度会更快。

var s = 'Lorem ipsum dolor sit amet, rhoncus nam sem feugiat, vel vel, viverra ultrices interdum. Volutpat ac congue. Lacinia sit donec quis facilisi, magna cubilia volutpat lectus fusce ligula quis, sit sed vivamus eget mauris quisque, aenean aenean nec litora litora massa, malesuada turpis pretium. Magnis metus nulla mauris dictum ligula, odio facilisis nullam laoreet. Aliquam tincidunt enim sit dolor mi. Duis malesuada pede, tortor consectetuer facilisis massa et leo vel. Eget fames tellus mi. Suscipit tincidunt fusce lacus convallis, ornare eu sed eu gravida interdum. Vivamus ipsum, maecenas penatibus, lacus posuere, eu cum, mauris ea libero elit. Libero blandit mattis mi sapien, iaculis wisi sit convallis, est in libero, elementum cras in a cum a vestibulum'

for (var i = 0; i < 3; i++) {
  s += s;
}

start = new Date().getTime();
s.match(/.*AAA.*/i)
stop = new Date().getTime();
console.log("AAA took " + (stop - start) + " ms")

start = new Date().getTime();
s.match(/.*BBB.*/i)
stop = new Date().getTime();
console.log("BBB took " + (stop - start) + " ms")

start = new Date().getTime();
s.match(/.*CCC.*/i)
stop = new Date().getTime();
console.log("CCC took " + (stop - start) + " ms")

start = new Date().getTime();
s.match(/.*(AAA|BBB|CCC).*/i)
stop = new Date().getTime();
console.log("Combined took " + (stop - start) + " ms")

于 2018-05-03T11:39:53.730 回答