你能解释为什么这些正则表达式很慢

时间:2018-05-03 11:37:27

标签: javascript regex

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

测量时间



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")
&#13;
&#13;
&#13;

以上打印

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

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

2 个答案:

答案 0 :(得分:1)

/AAA/i/BBB/i/CCC/i/.*(AAA|BBB|CCC).*/i的不同之处不仅在于使用交替捕获组,还在.*模式中。

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

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

通过可视化的回溯步骤查看this regex debugger

因此,查找行/字符串是否包含3个备选方案中的任何一个的最佳方法就是使用

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

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

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

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

答案 1 :(得分:0)

您之前的测试中未包含.*,导致match很快返回错误。

在这里,您可以看到每个测试都需要30ms才能运行。

全局仍然只需40ms

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

&#13;
&#13;
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")
&#13;
&#13;
&#13;