这个正则表达式可以进一步优化吗?

时间:2011-08-18 23:32:41

标签: java regex optimization scala

我写了这个正则表达式来解析srt文件中的条目。

(?s)^\d++\s{1,2}(.{12}) --> (.{12})\s{1,2}(.+)\r?$

我不知道它是否重要,但这是使用Scala编程语言(Java引擎,但文字字符串,以便我不必加倍反斜杠)。

使用s{1,2}是因为某些文件只有换行符\n而其他文件会有换行符和回车符\n\r 第一个(?s)启用DOTALL模式,以便第三个捕获组也可以匹配换行符。

我的程序基本上使用\n\r?\n作为分隔符来破坏srt文件,并使用Scala nice模式匹配功能读取每个条目以进行进一步处理:

val EntryRegex = """(?s)^\d++\s{1,2}(.{12}) --> (.{12})\s{1,2}(.+)\r?$""".r

def apply(string: String): Entry = string match {
  case EntryRegex(start, end, text) => Entry(0, timeFormat.parse(start),
    timeFormat.parse(end), text);
}

示例条目:

一行:

1073
01:46:43,024 --> 01:46:45,015
I am your father.

两行:

160
00:20:16,400 --> 00:20:19,312
<i>Help me, Obi-Wan Kenobi.
You're my only hope.</i>

问题是,分析器告诉我这个解析方法是我的应用程序中最耗时的操作(它进行密集的时间数学运算,甚至可以比读取和解析文件的速度快几倍。条目)。

那么任何正则表达式向导都可以帮助我优化它吗?或者也许我应该牺牲正则表达式/模式匹配简洁性并尝试旧学校java.util.Scanner方法?

干杯,

4 个答案:

答案 0 :(得分:4)

(?s)^\d++\s{1,2}(.{12}) --> (.{12})\s{1,2}(.+)\r?$

在Java中,$表示输入结束或紧接在输入结束之前的换行符的开头。 \z意味着明确地结束输入,因此如果这也是Scala中的语义,则\r?$是多余的,$也可以。如果你真的只想要一个CR而不是CRLF,那么\r?\z可能会更好。

(?s)也应该使(.+)\r?多余,因为+贪婪,.应始终展开以包含\r。如果您不希望第三个捕获组中包含\r,请将匹配设为懒惰:(.+?)而不是(.+)

也许

(?s)^\d++\s\s?(.{12}) --> (.{12})\s\s?(.+?)\r?\z

正则表达式的其他优秀高性能替代方案,将在JVM&amp; |中运行CLR包括JavaCCANTLR。有关仅Scala解决方案,请参阅http://jim-mcbeath.blogspot.com/2008/09/scala-parser-combinators.html

答案 1 :(得分:2)

我不乐观,但这里有两件事要尝试:

  1. 你可以做的就是将(?s)移到你需要之前。
  2. 删除\ r?$并使用贪婪的.++作为文字.+

    ^ \ d ++ \ s {1,2}(。{12}) - &gt; ({12})\ S {1,2}(αS)(。++)$

  3. 要真正获得良好的性能,我会重构代码和正则表达式以使用findAllIn。当前代码正在为文件中的每个Entry执行一个正则表达式。我想单个findAllIn正则表达式会表现得更好......但也许不是......

答案 2 :(得分:2)

检查出来:

(?m)^\d++\r?+\n(.{12}) --> (.{12})\r?+\n(.++(?>\r?+\n.++)*+)$

此正则表达式匹配完整的.srt文件条目到位。您不必先在换行符上拆分内容;这是对资源的巨大浪费。

正则表达式充分利用了这样一个事实,即只有一个行分隔符(\n\r\n)分隔条目中的行(多行分隔符用于将条目彼此分开)。使用\r?+\n代替\s{1,2}意味着当您只想匹配一个时,您绝不会意外匹配两个行分隔符(\n\n)。

这样,您也不必依赖于.模式中的(?s)。 @Jacob是对的:它并没有真正帮助你,而且它正在扼杀你的表现。但(?m)模式有帮助,无论是正确还是表现。

您提到java.util.Scanner;这个正则表达式可以很好地与findWithinHorizon(0)一起使用。但是,如果Scala不提供一种好的,惯用的方式来使用它,我会感到惊讶。

答案 3 :(得分:1)

我不会使用java.util.Scanner甚至字符串。只要您可以假设文件的UTF-8编码(或缺少unicode),您所做的一切都将在字节流上完美运行。你应该能够将速度提高至少5倍。


编辑:这只是很多低级别的字节和索引。这里的内容松散地基于我以前做过的事情,看起来大约快2到5倍,具体取决于文件大小,缓存等。我不是在这里解析日期,只是返回字符串,我假设文件足够小以适合单个存储器块(即&lt; 2G)。这是相当谨慎的;如果你知道,例如,日期字符串格式总是正常的话,那么解析可以更快(只计算第一行数字后的字符)。

import java.io._
abstract class Entry {
  def isDefined: Boolean
  def date1: String
  def date2: String
  def text: String
}
case class ValidEntry(date1: String, date2: String, text: String) extends Entry {
  def isDefined = true
}
object NoEntry extends Entry {
  def isDefined = false
  def date1 = ""
  def date2 = ""
  def text = ""
}

final class Seeker(f: File) {
  private val buffer = {
    val buf = new Array[Byte](f.length.toInt)
    val fis = new FileInputStream(f)
    fis.read(buf)
    fis.close()
    buf
  }
  private var i = 0
  private var d1,d2 = 0
  private var txt,n = 0
  def isDig(b: Byte) = ('0':Byte) <= b && ('9':Byte) >= b
  def nextNL() {
    while (i < buffer.length && buffer(i) != '\n') i += 1
    i += 1
    if (i < buffer.length && buffer(i) == '\r') i += 1
  }
  def digits() = {
    val zero = i
    while (i < buffer.length && isDig(buffer(i))) i += 1
    if (i==zero || i >= buffer.length || buffer(i) != '\n') {
      nextNL()
      false
    }
    else {
      nextNL()
      true
    }
  }
  def dates(): Boolean = {
    if (i+30 >= buffer.length) {
      i = buffer.length
      false
    }
    else {
      d1 = i
      while (i < d1+12 && buffer(i) != '\n') i += 1
      if (i < d1+12 || buffer(i)!=' ' || buffer(i+1)!='-' || buffer(i+2)!='-' || buffer(i+3)!='>' || buffer(i+4)!=' ') {
        nextNL()
        false
      }
      else {
        i += 5
        d2 = i
        while (i < d2+12 && buffer(i) != '\n') i += 1
        if (i < d2+12 || buffer(i) != '\n') {
          nextNL()
          false
        }
        else {
          nextNL()
          true
        }
      }
    }
  }
  def gatherText() {
    txt = i
    while (i < buffer.length && buffer(i) != '\n') {
      i += 1
      nextNL()
    }
    n = i-txt
    nextNL()
  }
  def getNext: Entry = {
    while (i < buffer.length) {
      if (digits()) {
        if (dates()) {
          gatherText()
          return ValidEntry(new String(buffer,d1,12), new String(buffer,d2,12), new String(buffer,txt,n))
        }
      }
    }
    return NoEntry
  }
}

现在您已经看到了,您是否很高兴正则表达式解决方案如此快速地编码?