经过大量迭代后,Java while循环会随着时间的推移而显着减慢

时间:2015-07-20 14:52:58

标签: java while-loop time-complexity

我的程序在while循环中逐行读取文本文件。然后它处理每一行并提取要在输出中写入的一些信息。它在while循环中所做的一切都是O(1),除了两个我认为是O(N)的ArrayList indexOf()方法调用。该程序在开始时以合理的速度运行(每100秒1M行),但随着时间的推移它会大幅减速。我在输入文件中有70 M行,因此循环迭代7000万次。从理论上讲,这应该需要大约2个小时,但实际上需要13个小时。问题在哪里?

以下是代码段:

BufferedReader corpus = new BufferedReader(
            new InputStreamReader(
                        new FileInputStream("MyCorpus.txt"),"UTF8"));

Writer outputFile = new BufferedWriter(new OutputStreamWriter(
            new FileOutputStream("output.txt"), "UTF-8"));

List<String> words = new ArrayList();
//words is being updated with relevant values here   

LinkedHashMap<String,Integer> DIC = new LinkedHashMap();
//DIC is being updated with relevant key-value pairs here    

String line = ""; 
while ((line = corpus.readLine()) != null)
    String[] parts = line.split(" ");
    if (DIC.containsKey(parts[0]) && DIC.containsKey(parts[1])) {

        int firstIndexPlusOne = words.indexOf(parts[0])+ 1;
        int secondIndexPlusOne = words.indexOf(parts[1]) +1;

        outputFile.write(firstIndexPlusOne +" "+secondIndexPlusOne+" "+parts[2]+"\n");
        } else { 
            notFound++;
            outputFile.write("NULL\n");
        }
    }
outputFile.close();

5 个答案:

答案 0 :(得分:2)

我假设您在前往words ArrayList时添加文字。

您正确地说明words.indexOfO(N),这就是您的问题的原因。当N增加(您在列表中添加单词)时,这些操作会花费更长时间。

为避免这种情况,请将列表排序并使用binarySearch

要对其进行排序,请在每个单词上使用binarySearch以确定插入位置的位置。这会使您的复杂性从O(n)升级到O(log(N))

答案 1 :(得分:0)

我认为您的问题之一是该行:

outputFile.write(firstIndexPlusOne +" "+secondIndexPlusOne+" "+parts[2]+"\n");

由于字符串是不可变的,因此会使内存变得混乱。此外,也许尝试在循环中每回合刷新写缓冲区它可能会改善一点(我的假设在这里)

尝试类似:

    String line = ""; 
    StringBuilder sb = new StringBuilder();
    while ...
    ...
      sb.append(firstIndexPlusOne);
      sb.append(" ");
      sb.append(secondIndexPlusOne);
      sb.append(" ");
      sb.append(parts[2]);
      sb.append("\n");
      outputFile.write(sb.toString());
      sb.setLength(0);
      outputFile.flush();

另外,也许读起来很好:Tuning Java I/O Performance (Oracle)

答案 2 :(得分:0)

我认为,单词意味着收集独特的单词,因此使用Set。

Set<String> words = new HashSet<>();
Map<String, Integer> DIC = new HashMap<>();

DIC看起来像频率表,在这种情况下dic.keySet()words相同。 LinkedHashMap维护一个额外的列表,以保持条目按插入顺序排序。

编写单独的字符串,而不是先创建新字符串更快。

   outputFile.write(firstIndexPlusOne);
   outputFile.write(" ");
   outputFile.write(secondIndexPlusOne);
   outputFile.write(" ");
   outputFile.write(parts[2]);
   outputFile.write("\n");

答案 3 :(得分:0)

如果语料库和单词列表都已排序,则words.indexOf(..)调用执行的线性搜索在每次迭代中都会变慢。

在处理语料库之前,从单词列表中构建HashMap(..)甚至可以解决问题。这样做可能是一个好主意,即使这不是问题。

答案 4 :(得分:0)

假设您在循环中既不更新words也不更新DIC,显然当DIC.containsKey(parts[0]) && DIC.containsKey(parts[1])求值为true时,消耗的运行时间最多。

如果您的问题是“为什么会变慢”,而不是“我怎样才能加快速度”,我建议您先取出文件的前10M行,将其复制到另一个文件中并复制它们你收到70M行,包括你的前10M行的副本。然后,执行您的代码。如果一次又一次地检查相同的内容,它会变慢,你可以查看关于字符串构建器等的其他答案。

如果您没有遇到减速,那么显然它取决于您的70M文件的实际内容。适当地,对于原始文件的剩余60M行,DIC.containsKey(parts[0]) && DIC.containsKey(parts[1])更频繁地求值为true,因此内循环执行得更频繁,花费更多时间。

在后一种情况下,我怀疑你可以通过应用单次写入来欺骗I / O负载,从而获得性能增益,但当然我可能在那里非常错误。你必须尝试。但首先,我建议探索问题的根源,我认为这个问题在于文件内容的结构。在了解了代码相对于给定输入的执行情况之后,您可以尝试优化(尽管我会尝试将整个字符串保留在内存中并在循环之后在一个操作中写入其内容,而不是执行很多小写操作)