【问题标题】:Can I improve performance of this regular expression further我可以进一步提高这个正则表达式的性能吗
【发布时间】:2015-11-23 11:07:57
【问题描述】:

我正在尝试从线程转储文件中获取线程名称。 线程名称通常包含在每个线程转储的第一行的“双引号”中。 它可能看起来很简单:

"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]

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

所以我想出了另一个执行相同的正则表达式:"([^\"])"。口头上我们可以将此正则表达式描述为 “捕获任意数量的包含在双引号内的非双引号字符”。我没有找到比这更快的正则表达式。它不执行任何回溯,因此它需要最少的步骤,如 here 所示。

我在上面告诉了我的同事。他想出了另一个:"(.*?)"。我不明白它是如何工作的。它执行的回溯比第一个要少得多,但比第二个要慢一点,如here 所示。 不过

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

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

【问题讨论】:

  • 是否需要匹配" + substring having no quote + "之类的子字符串?
  • 我认为你应该使用.*? 这会使搜索变得懒惰。我认为您当前的正则表达式存在缺陷。如果线程名称后面的行中有"some text here",则最后一个" 将被映射。
  • @VinodMadyalkar:您建议的是效率最低的解决方案之一。惰性匹配有一些非常重要的缺点。否定字符类解决方案是最好的。
  • @stribizhev - 但是看看字符串,贪婪会涉及很多回溯。如果线程名称后面还有另一个"" 怎么办? Thread 名称从 String 的开头开始,值得回溯吗?
  • @VinodMadyalkar: "([^"]*+)" 是匹配"no-quotes-here" 类字符串的最佳正则表达式。

标签: java regex performance


【解决方案1】:

简要说明及解决办法

"(.*)" 正则表达式涉及大量回溯,因为它会找到第一个 ",然后抓取整个字符串并回溯寻找最接近字符串末尾的 "。由于您有一个更接近开头的引用子字符串,因此回溯比 "(.*?)" 更多,因为这个 lazy 量词 *? 使正则表达式引擎在第一个 " 之后寻找最接近的 "找到了。

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

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

"([^"]*+)"

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

"([^"]*+)

regex demo

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

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

  • " - 查找字符串左侧的第一个 " 符号
  • ([^"]*+) - 匹配并捕获除 " 之外的零个或多个符号到第 1 组中,并且一旦引擎找到双引号,匹配立即返回,无需回溯。

量词

更多信息quantifiers from Rexegg.com

A*零个或多个as,尽可能多(贪婪),如果引擎需要回溯,放弃字符(驯服)
A*?零个或多个as,尽可能少,以允许整体要匹配的模式(懒惰)
A*+ 零个或多个尽可能多(贪婪),如果引擎尝试回溯(占有)则不放弃字符

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

我建议阅读更多关于为什么 Lazy Quantifiers are ExpensiveNegated Class Solution 处理您的输入字符串非常安全和快速的信息(您只需匹配一个引号,然后是非引号,然后是最后一个引号)。

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

  • 贪婪的"(.*)" 解决方案是这样工作的:从左到右检查每个符号以寻找",一旦找到,就将整个字符串抓取到末尾并检查每个符号是否等于"。因此,在您的输入字符串中,它会回溯 160 次。

由于下一个"不远,所以回溯步数比贪心匹配要少很多。

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

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

对于当前的表达方式,虽然差别不大。

【讨论】:

  • * 惰性量词* 是 非贪婪量词 的糟糕名称。它们并不“懒惰”,就像这个词在计算中的其他任何地方都适用
  • @Borodin - 我不同意。术语:“懒惰”恰当而简洁地描述了这个修饰符的行为。此外,该术语在 Jeffrey Friedl 的经典作品中广泛使用:Mastering Regular Expressions。放下手,我读过的最有用的书。
  • @ridgerunner:当然欢迎你提出你的意见,但我不同意。 A 在编程中,一个lazy操作一般是延迟到需要它的效果,这与非延迟操作无关。贪婪的正则表达式量词。 B 在英文中,lazy并不是greedy的反义词。 C 不管你怎么评价弗里德尔的书,想象它所说的一切都是真实的、无可辩驳的、无可比拟的,这很奇怪。尽管基本原则仍然适用,但它现在也已经过时了。
  • 我很好奇this question 是否可以用一个正则表达式而不是我的两个正则表达式解决方案来回答...?
猜你喜欢
  • 2011-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-27
  • 2012-08-28
相关资源
最近更新 更多