【问题标题】:Regular Expression performing poorly in Ruby compared to JavaScript与 JavaScript 相比,正则表达式在 Ruby 中的表现较差
【发布时间】:2011-11-29 04:43:16
【问题描述】:

最近,我开始在我的 Rails 模型中使用来自 JQuery validation 插件的电子邮件验证正则表达式。

EMAIL_REGEXP=/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i

"aaaaa.bbbbbb.ccccc.ddddd@gmail.com".match EMAIL_REGEXP  # returns immidiately
"aaaaa.bbbbbb.ccccc.ddddd@gmail".match EMAIL_REGEXP  # takes a long time

当一封无效的电子邮件有很多点分隔的标记(例如:first.middle.last@gmail)时,正则表达式需要很长时间。相同的表达式 works without JavaScript 中的任何明显延迟。

为什么 Ruby 和 JavaScript 正则表达式解析器的性能会有如此大的差异?有什么办法可以缩短响应时间吗?

我正在使用 Ruby 1.8.7。我在 Ruby 1.9.2 上没有看到同样的问题。

注意

我知道正则表达式很长。由于它是jQuery使用的,所以我想到了使用它。我总是可以将它改回更简单的正则表达式,如here 所示。我的问题主要是关于找出为什么相同的正则表达式在 JS 中要快得多的原因。

参考:

JQuery Validation Plugin Source

Sample form with jQuery email validation

【问题讨论】:

  • 这是一个巨大的正则表达式。为什么不使用电子邮件解析器?
  • @Blender 如果您提供链接,评论质量会大大提高
  • 这个正则表达式取自官方jQuery插件,被广泛使用。所以我认为这是一条安全的路线。如果我不能让 RegExp 工作,我可能会走自定义验证的路线。
  • 另一个好问题是,您想通过匹配电子邮件地址来完成什么?你无法证明它是否有效;因为即使模式匹配,地址也可能不存在。测试电子邮件是否有效的公认方法是向该地址发送消息要求回复,然后查看是否收到回复。见stackoverflow.com/q/1910340/128421
  • 哪个 ruby​​ 版本? 1.9 处理正则表达式与 1.8 非常不同

标签: javascript ruby-on-rails ruby regex


【解决方案1】:

不知道为什么 1.8.7 的正则表达式解析器比 1.9.2 的 JS 或 Oniguruma 的解析器慢得多,但这个特殊的正则表达式可能会受益于用原子组包装其前缀,包括 @ 符号像这样:

EMAIL_REGEXP = /
  ^
  (?>(( # atomic group start
    ([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+
    (\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*
   )
   |
   (
     (\x22)
     (
       (((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?
       (
         ([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])
         |
         (\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))
       )
     )*
     (((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?
     (\x22)
   )
  )
  @) # atomic group end
  (
    (
      ([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])
      |
      (
        ([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])
        ([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*
        ([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])
      )
    )
    \.
  )+
  (
    ([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])
    |
    (
      ([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])
      ([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*
      ([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])
    )
  )
  $
  /xi

puts "aaaaa.bbbbbb.ccccc.ddddd@gmail.com".match EMAIL_REGEXP  # returns immediately
puts "aaaaa.bbbbbb.ccccc.ddddd@gmail".match EMAIL_REGEXP  # takes a long time

在这种情况下,原子组应防止解析器在匹配 @ 符号后面的部分失败时返回到字符串的第一部分。它提供了显着的加速。不过,我不能 100% 确定它不会破坏正则表达式逻辑,所以我会很感激任何 cmets。

另一件事是使用非捕获组,当您不需要对组进行反向引用时,通常应该更快,但在这种情况下它们并没有带来任何明显的改进。

【讨论】:

    【解决方案2】:

    问题可能在于您的正则表达式包含一个贪婪的量词,因此 Ruby 作为这些量词需要尝试检查所有组合。解决方案可能是使用Possessive Quantifiers,因此查找会更快,但它会更改正则表达式,因此某些字符串将不再匹配。简短示例(来自维基百科):

    'aaaaaaaaaaaaaaaaaaaaaaaaa' =~ /(a+a+)/ => match
    'aaaaaaaaaaaaaaaaaaaaaaaaa' =~ /(a++a+)/ => not match
    

    区别在于查找过程和贪婪量词引擎如果不匹配则尝试回溯,而所有格量词引擎从不回溯。

    【讨论】:

    • 我原以为正则表达式会因为贪婪的量词而变慢。让我吃惊的是,对于相同的 reg-exp,JS 和 Ruby 执行时间的性能差异。
    • 我在tryruby 试过你的例子,代码执行没有大的延迟。顺便说一句,第二个字符串不匹配。
    • Tryruby 使用具有新的正则表达式引擎的 1.9.2,并且很好地处理了这个正则表达式。另一方面,1.8.7 只是在问题的第二个示例中死亡。
    • Tryruby 使用 ruby​​ 1.9.2。我只在 1.8.7 上看到这个问题。第二个字符串不应该匹配。
    猜你喜欢
    • 2017-09-27
    • 1970-01-01
    • 1970-01-01
    • 2011-08-06
    • 2019-11-29
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    相关资源
    最近更新 更多