找到,然后用java中的反向替换

时间:2012-06-29 21:05:56

标签: java regex performance

我在Java中使用非常大的.txt蛋白质文件数据库。蛋白质具有一般结构,但不是一个足够均匀的硬编码“从startIndex到endIndex,反向和替换”。唯一真正的一致性是它们由>分隔,例如:

...WERINWETI>gi|230498 [Bovine Albumin]ADFIJWOENAONFOAIDNFKLSADNFATHISDATFDAIFJ>sp|234235 (human) AGP1 QWIQWONOQWNROIWQRNOQWIRNSWELLE>gi|...等等。

正如你所看到的,虽然实际的蛋白质序列(所有大写的长链)是均匀的,因为它们是大写的链,但除此之外,前面的描述几乎可以是任何东西(有很多次不是描述和序列之间的空格)。我的程序需要做的是将原始文本复制到新文件,然后通过,在每个r-之后添加>(例如...EERFDS>r-gi|23423...)以及仅反转链资本。完成该过程后,我需要将其附加到原始文本的末尾。

我已经完成了r-功能,实际上我已经完成了逆转和追加,但效率不高。接受这种治疗的数据库是大量的,我的程序需要太长时间。事实上,我不知道需要多长时间,因为我永远不会让它完成。我等了1个小时才结束了。这是我使用正则表达式(内置模式类)(计算密集的部分)进行反转的算法:

Pattern regexSplit = Pattern.compile(">");
String[] splits = regexSplit.split(rDash.toString());
StringBuilder rDashEdited = new StringBuilder();
Pattern regexProtein = Pattern.compile("[A-Z]{5,}");

for (int splitIndex = 1; splitIndex < splits.length; splitIndex++) {
    Matcher rDashMatcher = regexProtein.matcher(splits[splitIndex]);
    rDashMatcher.find();
    StringBuffer reverser = new StringBuffer(rDashMatcher.group());
    rDashEdited.append(rDashMatcher.replaceAll(reverser.reverse().toString()) + ">");
}
System.out.println(">" + rDashEdited);

所以,基本上我将rDash(这是一个包含所有原始蛋白质的StringBuilder,其中包含>r-,但尚未经过逆转)转换成每个单独的蛋白质并将它们添加到一个String数组。然后我遍历数组中的每个字符串,查找大于5个字母的大写字母链,将匹配添加到StringBuffer,反转它,然后用反向替换正向版本。请注意,此算法适用于较小的文本文件。

是否有更强大的正则表达式可以消除分裂/遍历数组的需要?当我尝试时,replaceAll()调用替换了所有下游蛋白质,与该组中的FIRST蛋白质相反。为了好玩,我用System.out.println(rDashMatcher.groupCount())检查了它,并为该组中的每种蛋白质打印了0。任何人都可以帮助我提高效率/强大的正则表达式吗?对我来说这是一个相当新的概念,但它让我想起了MATLAB中的矢量化(只有字母)。

4 个答案:

答案 0 :(得分:2)

我在这里投入了10,000,000条记录(大约379MB文本文件),花了1:06分钟。(4core athlon,几年)

由于分隔符位于元素的中间,因此大的if树会处理只有一半的末端。

public void readProteins(BufferedReader br, BufferedWriter bw) throws IOException
{     
  Pattern regexSplit = Pattern.compile(">");
  Pattern proteinPattern = Pattern.compile("(.*?)([A-Z]{5,})");
  Matcher m;
  Scanner s = new Scanner(br);
  s.useDelimiter(regexSplit);         
  while (s.hasNext())
  {
      StringBuffer sb = new StringBuffer();
      String protein = s.next();
      m = proteinPattern.matcher(protein);            
      if (m.find())
          sb.append(m.group(2)).reverse().append(">r-").insert(0, m.group(1));
      else
          sb.append(protein);
      );          
  }
  bw.flush();
  bw.close();
}

答案 1 :(得分:1)

优化的一些想法:

  • 避免使用StringBuffer。 StringBuilder提供相同的功能和 是比较快的。
  • 而不是替换所有你可以使用 stringBuilder.replace(int start,int end,String str),即将 避免再次尝试匹配整个字符串中的模式。
  • 使用方法#b,您甚至可以跳过拆分继续搜索protiens 并在它来时替换它们。

最好与探查器一起运行,看看什么是消耗时间而不是猜测。例如,可以通过增加程序的内存或避免某些慢速文件系统等来提高性能。

答案 2 :(得分:1)

您不需要更强大的正则表达式,只需简化您的流程,这样您就不会一遍又一遍地处理相同的文本。在大多数情况下,这意味着使用Java的低级正则表达式API,即appendReplacement()appendTail()。通过将空字符串传递给appendReplacement(),我避免了自动处理反向引用。

注意我也是如何使用find()的。如果您发现自己正在调用find()(或matches()lookingAt())并且而非检查其返回值,则说明您做错了什么。这就是你如何知道比赛是否成功。

public static void main(String[] args) throws Exception
{
  // this I/O code is bare-bones so as not to distract from the fun stuff
  BufferedWriter bw = new BufferedWriter(new FileWriter("test_out.txt"));

  // I use a lookahead so the ">" doesn't get discarded
  Scanner sc = new Scanner(new File("test.txt")).useDelimiter("(?=>)");
  while (sc.hasNext())
  {
    bw.write(reverseCapBlocks(sc.next()));
  }
  sc.close();
  bw.close();
}

// cache these because recompiling them is fairly expensive
static final Pattern CAPS_PATTERN = Pattern.compile("\\b[A-Z]{5,}\\b");
static final Pattern BRACKET_PATTERN = Pattern.compile("^>");

static String reverseCapBlocks(String s)
{
  StringBuffer sb = new StringBuffer();
  Matcher m = CAPS_PATTERN.matcher(s);
  while (m.find())
  {
    // appends whatever was between the last match and this one
    // but hole off on appending the current match
    m.appendReplacement(sb, "");
    String temp = m.group();

    // do the reversing manually because it's trivial and it avoids
    // creating a new StringBuilder every time
    for (int i = temp.length() - 1; i >= 0; i--)
    {
      sb.append(temp.charAt(i));
    }
  }
  // append whatever was left after the last match
  m.appendTail(sb);

  // if the chunk began with ">", add the "r-"
  return BRACKET_PATTERN.matcher(sb).replaceFirst(">r-");
}

我使用StringBuffer而不是StringBuilder,因为这是API所要求的,但这并不是什么大问题;关于StringBuffer效率低下的报道,虽然如此,但往往被夸大了。

答案 3 :(得分:0)

正如我在评论中提到的,您不应该将整个文件加载到内存中。这将导致内存交换进入并使程序变慢。

如果“蛋白质”的大小,即>分隔的字符串在内存中是可控的,这应该可以解决这个问题

    Scanner scanner = null;
    BufferedWriter writer = null;
    try {
        writer = new BufferedWriter(new FileWriter("output.txt"));
        scanner  = new Scanner(new BufferedReader(new FileReader("input.txt")));
        scanner.useDelimiter(">");
        while ( scanner.hasNext() ) {
           doReverseAndWriteToFile(scanner.next(), writer);
        }
    } finally {
        if ( scanner != null) {
            scanner.close();
        }
        if ( writer != null ) {
            writer.flush();
            writer.close();
        }
    }

doReverseAndWriteToFile()你应该把你的程序的第二部分(我没注意:-))。在此功能中,您还应该随时写入新文件。

如果你使用它,你一次只能在内存中使用“bufferSize”+“一个蛋白长度”。

看看这是否会加快它...否则你必须寻找其他地方。