代码实际上是在 Scala (Spark/Scala) 中,但根据文档,库 scala.util.matching.Regex 委托(delegate)给 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(.+?)
当我将此正则表达式添加到列表中时,应用程序需要很长时间才能完成日志处理。为了让您了解延迟的程度,在我的 Spark 集群上,一百万行的典型批处理需要不到 5 秒的时间来匹配/提取。如果我添加上面的表达式,一个批处理需要一个小时!
在我的代码中,我尝试了几种匹配正则表达式的方法:
if ( (regex findFirstIn log).nonEmpty ) { 做某事 }
val allGroups = regex.findAllIn(log).matchData.toList if (allGroups.nonEmpty) { 做某事 }
if (regex.pattern.matcher(log).matches()){做某事}
当上面提到的正则表达式添加到正则表达式列表中时,所有三个都表现不佳。对于提高正则表达式性能或更改正则表达式本身有什么建议吗?
标记为 duplicate 的问答有一个我发现很难理解的链接。如果引用的软件 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 的 #
,并再次匹配任意数量的非 #
。重复最后一句话任意多次。
它的一个小问题是它也匹配一个空序列,而且我看不到修复它的简单方法。
关于Java,惰性表达式的正则表达式性能较差,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30266109/