正则表达式性能问题 - 亵渎过滤器

时间:2012-02-23 22:47:01

标签: java regex performance

我正在制作一个亵渎过滤器(我知道这个坏主意),而我正试图用Java中的正则表达式来做。

现在这里是我的正则表达式示例字符串,这将过滤2个单词,foo和bar。

(?i)f(?>[.,;:*`'^+~\\/#|]*+|.)o(?>[.,;:*`'^+~\\/#|]*+|.)o|b(?>[.,;:*`'^+~\\/#|]*+|.)a(?>[.,;:*`'^+~\\/#|]*+|.)r

基本上,我忽略了大小写,然后我将(?>[.,;:*'^+~\\/#|]*+|.)放在诅咒词的每个字母之间,并在每个完整的诅咒词正则表达式之间放置|

它有效,但它很慢。

如果我在过滤器中有6个单词,它将在939,548纳秒内过滤相当长的字符串(500个字符)。当我有12岁时,它几乎是双打。

所以,每6个诅咒话约1毫秒。但我的过滤器将有数百(400左右)。 计算这个,过滤这个长字符串需要大约66ms。

这是我正在构建的聊天服务器,如果我有很多用户(比如5,000),1/5中有1个人在1秒内聊天(1000条聊天消息),我需要在大约1ms内过滤一条消息

我是否过多地询问了正则表达式?手工制作我自己的专用过滤器会更快吗?有没有办法优化这个?

我正在预编译正则表达式。

如果您想查看此正则表达式http://regexr.com?30454

的效果

更新:我还能做的另一件事就是在动作脚本中将客户端的聊天消息过滤掉。

更新:我认为实现这种性能程度的唯一方法是手动编码解决方案,而不使用正则表达式,所以我将不得不做一个更基本的过滤器。

4 个答案:

答案 0 :(得分:10)

回答你的问题“我是否要求过多的正则表达式?” - 是的

我花了两年多的时间来处理使用正则表达式的亵渎过滤器,最后放弃了。在这段时间里,我尝试了所有这些:

  • 预编译
  • 字符类(标点符号,空格等)
  • 非捕获组(如上所述,可以大大减少内存并提高速度)
  • 结合类似的regexp(也在上面提到)
  • 修剪空格(str.trim())
  • 案件处理(str.toLowerCase())
  • 打包和解包空格(将多个相邻的空格转换为单个空格,反之亦然)
  • 编写我自己的自定义regexp引擎(非常不推荐,因为它很复杂而且不可扩展)

没有什么运作良好,随着我的黑名单增长,我的系统变慢了。最后,我放弃并实现了一个线性分析过滤器,它现在是CleanSpeak的核心部分,my company's profanity filtering product

我们发现,一旦我们停止使用正则表达式,我们就能够进行一些出色的多线程和其他优化,从每秒处理600-700条消息到每秒10,000多条消息。

最后,我们还发现执行线性分析使得过滤器更加准确,并使我们能够解决“scunthrope问题”以及人们在此处的评论中提到的许多其他问题。

你绝对可以尝试我上面提到的所有内容,看看你是否可以提升你的表现,但这是一个难以解决的问题,因为正则表达式并非真正用于语言分析。它们是为文本分析而设计的,这是一个非常不同的问题。

答案 1 :(得分:4)

您可以使用任何内置字符类,例如

 \bf\W?o\W?o\W?\b

用字母之间的任何非字母来检测“foo”,但不是“食物”或“snafoo”(原文如此)

然而,这方面的弱点是“_”算作单词字符: - (

我认为更有前途的方法是使用一个带有一些误报的简单快速过滤器,然后针对更严格的过滤器重新测试肯定。除非您的用户是完全的便盆,否则不应该进行所有那么多详细的检查。

更新:我回家后想到这一点,但是 Qtax 首先到达那里(见其他答案) - 先尝试删除所有标点符号,然后运行简单的单词模式在文本上。这应该使单词模式更加简单和快速,特别是当你有很多单词需要测试时。

最后请注意,在[]内你不需要转义正则表达式特殊字符,所以:

[.,;:*`'^+~\\/#|]

没问题(反斜杠仍然需要转义)

答案 2 :(得分:1)

当你有很多单词时,按照他们的第一个相同的字符对它们进行分组,你会看到添加单词的时间增加不到线性时间。

我的意思是,如果你有两个单词“foobar”和“fook”使正则表达形成foo(?:bar|k)

使用非回溯组而不是非捕获组可能会提高性能。即将(?:...)替换为(?>...)

另一个建议可能是先删除字符串中的所有标点符号,然后再应用一个更简单的表达式。

此外,如果可以,请尝试在较长的字符串上应用表达式。因为这可能比一次做一条消息更快。也许结合几条消息进行第一次检查。

答案 3 :(得分:0)

您可以尝试替换所有

[.,;:*`'^+~\\/#|]+

使用空字符串,然后只需检查

\b(foo|bar)\b

<强>更新

如果你对空间f( *+)o\1o|b( *+)a\2r

更偏执

或更偏执的一般f([^o]?)o\1o|b([^a]?)a\2r