我可以进一步提高这个正则表达式的性能吗?

我试图从线程转储文件中获取线程名称。 线程名称通常包含在每个线程转储的第一行的“双引号”中。 它可能看起来如下所示:

"THREAD1" daemon prio=10 tid=0x00007ff6a8007000 nid=0xd4b6 runnable [0x00007ff7f8aa0000] 

或者大到如下:

 "[STANDBY] ExecuteThread: '43' for queue: 'weblogic.kernel.Default (self-tuning)'" daemon prio=10 tid=0x00007ff71803a000 nid=0xd3e7 in Object.wait() [0x00007ff7f8ae1000] 

我写的正则表达式很简单: "(.*)" 。 它将双引号内的所有内容作为一组捕获。 然而,这会导致严重的回溯,因此需要很多步骤,如此处所示。 在口头上,我们可以将此正则表达式解释为“捕获任何包含在双引号内的任何内容”

所以我提出了另一个执行相同的正则表达式: "([^\"])" 。我们可以将这个正则表达式描述为”捕获任意数量的双引号括起来的非双引号字符“ 。我没有发现任何快速正则表达式。它不执行任何回溯,因此它需要最少的步骤,如此处所示。

我把这个告诉了我的同事。 他想出了另一个: "(.*?)" 。 我没弄明白它是如何运作的。 与第一个相比,它执行的回溯相当少,但比第二个慢一点,如此处所示。 然而

  • 我不明白为什么回溯会提前停止。
  • 我明白了? 是一个量词,意味着once or not at all 。 但是我不明白once or not at all这里once or not at all使用过。
  • 事实上,我无法猜测我们如何口头描述这个正则表达式。

我的同事试图解释我,但我仍然无法完全理解它。 谁有人解释一下?

简要说明和解决方案

"(.*)"正则表达式涉及大量的回溯,因为它找到第一个"然后抓取整个字符串并回溯寻找最接近字符串结尾的”。 因为你有一个更接近开头的带引号的子串,所以有更多的回溯而不是"(.*?)"作为这个懒惰的量词*? 使正则表达式引擎找到最接近的"在第一个之后"找到。

否定的字符类解决方案"([^"]*)"是3中最好的,因为它不必抓取所有字符,只是除了"之外的所有字符。 但是,要停止任何回溯并使表达最终有效,您可以使用占有量词

如果您需要匹配" + no quotes here + "等字符串,请使用

 "([^"]*+)" 

或者甚至在这种情况下你不需要匹配尾随引用:

 "([^"]*+) 

请参阅正则表达式演示

事实上,我无法猜测我们如何口头描述这个正则表达式。

后者"([^"]*+)正则表达式可以描述为

  • " – 从字符串的左边找到第一个"符号
  • ([^"]*+) – 匹配并捕获到第1组零个或多个符号而不是" ,尽可能多,并且一旦引擎找到双引号,匹配将立即返回,无需回溯。

量词

有关量词的更多信息,请访问Rexegg.com :

A*零或更多As,尽可能多(贪婪),如果引擎需要回溯(温顺),放弃字符
A*? 零或多个As,尽可能少地允许整个模式匹配(懒惰)
A*+零或更多As尽可能多(贪婪),如果引擎试图回溯(占有),则不放弃字符

如你所见, ? 它不是一个单独的量词,它是另一个量词的一部分。

我建议阅读更多关于为什么懒惰量词是昂贵的 ,并且否定类解决方案是非常安全和快速处理您的输入字符串(您只需匹配报价,然后是非引号,然后是最终报价)。

.*?之间的区别.*?.*[^"]*+量词

  • 贪婪的"(.*)"解决方案的工作方式如下:从左到右检查每个符号,查找" ,一旦找到,就抓住整个字符串直到结尾并检查每个符号是否等于" 。 因此,在输入字符串中,它会回溯160次。

在此处输入图像描述

  • 懒惰的"(.*?)"解决方案的工作方式如下:引擎找到第一个"然后在模式中前进,并在THREAD1中对T的下一个令牌(即" )进行THREAD1 。 这失败了,所以引擎回溯并允许.*? 通过一个项目扩展其匹配,以便它匹配T 再一次,发动机在模式中前进。 它现在尝试"对抗THREAD1中的H这失败了,所以引擎回溯并允许.*?扩展并匹配H 然后该过程重复进行 – 引擎前进,失败,回溯,允许懒惰.*?通过一个项目扩展匹配,前进,失败等等。对于每个匹配.*?字符,引擎必须回溯。从计算的角度来看,这个匹配一个项目,推进,失败,回溯,扩张是“昂贵的”。

由于下一个"不远”,回溯步骤的数量远远少于贪婪匹配。

在此处输入图像描述

  • 具有否定字符类的占有量词解决方案"([^"]*+)"就像这样:引擎找到最左边的" ,然后抓取所有不是"直到第一个"字符。 否定的字符类[^"]*+贪婪地匹配零个或多个不是双引号的字符。因此,我们保证点星将永远不会跳过第一个遇到的" 。 这是在某些分隔符之间进行匹配的更直接有效的方法。 请注意,在这个解决方案中,我们可以完全信任量化[^"]* 。即使它是贪婪的,也不存在[^"]匹配过多的风险,因为它与""相互排斥。来自正则表达式风格指南的对比原理 [见源代码] 。

注意,占有量量化器不会让正则表达式引擎回溯到子表达式,一旦匹配,“由于正则表达式引擎遇到一些”不便“而成为”无法“重新排序的一个硬块之间的符号,它将会无法将任何字符从此文本块中移出。

对于当前表达式,它并没有产生很大的不同。

在此处输入图像描述