该代码实际上在Scala(Spark / Scala)中,但是根据文档,库scala.util.matching.Regex委托给java.util.regex。
从本质上讲,该代码从配置文件中读取一堆正则表达式,然后将它们与馈入Spark / Scala应用程序的日志进行匹配。一切工作正常,直到我添加了一个正则表达式以提取由制表符分隔的字符串,其中制表符已展平为“#011”(通过rsyslog)。由于字符串可以有空格,因此我的正则表达式如下:
(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)#011(.+?)
当我将此正则表达式添加到列表中时,该应用程序将花费大量时间来完成日志处理。为了让您大致了解延迟的大小,一百万行的典型批次用不到5秒的时间就能匹配/提取我的Spark集群。如果我在上面添加表达式,则批处理需要一个小时!
在我的代码中,我尝试了几种匹配正则表达式的方法:if ( (regex findFirstIn log).nonEmpty ) { do something }
val allGroups = regex.findAllIn(log).matchData.toListif (allGroups.nonEmpty) { do something }
if (regex.pattern.matcher(log).matches()){do something}
当上面提到的正则表达式添加到正则表达式列表中时,这三者均表现不佳。有什么建议可以改善正则表达式的性能或更改正则表达式本身吗?
标记为duplicate的Q / A有一个链接,我觉得很难理解。如果所引用的软件regexbuddy是免费的,或者至少在Mac上可以运行,则遵循本文可能会更容易。
我尝试使用负数前瞻,但无法弄清楚如何对字符串取反。代替/(.+?)#011/
的是类似/([^#011]+)/
的东西,只是说“#”或“ 0”或“ 1”取反。如何取消“#011”?即使在那之后,我不确定否定是否可以解决我的性能问题。
最佳答案
最简单的方法是在#011
上分割。如果要使用正则表达式,则确实可以取反字符串,但这很复杂。我会去一个原子团
(?>(.+?)#011)
一旦匹配,就不再有回溯。做完了,期待下一组。
否定字符串
#011
的补码是不是以#
开头,不是以#
开头且不以0
开头,还是以两者开头且不跟随的任何东西...您知道。我添加了一些空白以提高可读性: ((?: [^#] | #[^0] | #0[^1] | #01[^1] )+) #011
太可怕了,不是吗?与您的原始表达式不同,它匹配换行符(您并没有具体说明它们)。
一种替代方法是使用负前瞻:如果以下字符不是
(?!#011)
,则#011
匹配,但不吃任何东西,因此我们使用.
来吃一个字符: ((?: (?!#011). )+)#011
与仅使用原子组相比,这一切都非常复杂,并且性能可能较差。
最佳化
在我上面的正则表达式中,第一个是最好的。但是,正如Casimir et Hippolyte所写,还有改进的余地(系数1.8)
( [^#]*+ (?: #(?!011) [^#]* )*+ ) #011
它并不像看起来那么复杂。首先以原子方式匹配任何数量(包括零)的非
#
(后跟+
)。然后匹配一个#
后面不跟011,再匹配任意数量的non- #
。重复最后一句话任意次数。它的一个小问题是它也匹配一个空序列,我看不到一种简单的方法来修复它。