如何排序N个档案

时间:2019-02-14 10:48:37

标签: java algorithm

以下回答->

How do I sort very large files

在磁盘上N个已经排序的文件上,我只需要[[0 1 0 1 1 1 0 1] [1 0 1 1 0 1 0 0]] 函数, 我想将它们排序到一个大文件中,但我的限制是内存不超过Merge,因此我无法获取所有它们然后进行排序,首选使用Java

到目前为止,我尝试过下面的代码,但是我需要一种很好的方法来逐行遍历所有N个文件(内存中不超过K个LINES)+将排序后的最终文件存储到磁盘上

K lines in the memory (K < N)

致谢

2 个答案:

答案 0 :(得分:5)

您可以使用类似的东西

public static void mergeFiles(String target, String... input) throws IOException {
    String lineBreak = System.getProperty("line.separator");
    PriorityQueue<Map.Entry<String,BufferedReader>> lines
        = new PriorityQueue<>(Map.Entry.comparingByKey());
    try(FileWriter fw = new FileWriter(target)) {
        String header = null;
        for(String file: input) {
            BufferedReader br = new BufferedReader(new FileReader(file));
            String line = br.readLine();
            if(line == null) br.close();
            else {
                if(header == null) fw.append(header = line).write(lineBreak);
                line = br.readLine();
                if(line != null) lines.add(new AbstractMap.SimpleImmutableEntry<>(line, br));
                else br.close();
            }
        }
        for(;;) {
            Map.Entry<String, BufferedReader> next = lines.poll();
            if(next == null) break;
            fw.append(next.getKey()).write(lineBreak);
            final BufferedReader br = next.getValue();
            String line = br.readLine();
            if(line != null) lines.add(new AbstractMap.SimpleImmutableEntry<>(line, br));
            else br.close();
        }
    }
    catch(Throwable t) {
        for(Map.Entry<String,BufferedReader> br: lines) try {
            br.getValue().close();
        } catch(Throwable next) {
            if(t != next) t.addSuppressed(next);
        }
    }
}

请注意,此代码与问题代码不同,它处理标题行。像原始代码一样,它将删除输入行。如果不希望这样做,您可以删除DELETE_ON_CLOSE选项,并将整个阅读器的结构简化为
BufferedReader br = new BufferedReader(new FileReader(file));

它的内存与文件一样多。

虽然原则上可以在内存中保留更少的行字符串,并在需要时重新读取它们,但对于可观的少量节省却是性能的灾难。例如。由于您拥有N个文件名,因此调用此方法时,内存中已经有N个字符串。

但是,如果您想不惜一切代价减少同时保留的行数,则可以简单地使用问题中显示的方法。将前两个文件合并为一个临时文件,将该临时文件与第三个文件合并到另一个临时文件,依此类推,直到将临时文件和最后一个输入文件合并为最终结果。然后,您的内存(K == 2中最多有两个行字符串,节省的内存少于操作系统用于缓冲的内存,试图减轻这种方法的可怕性能。

同样,您可以使用上面显示的方法将K文件合并到一个临时文件中,然后将该临时文件与下一个K-1文件合并,依此类推,直到将临时文件与剩余的K-1个文件或更少的文件直到最终结果,以使内存消耗按K < N缩放。这种方法允许调整K以使其与N具有合理的比率,从而以内存换取速度。我认为,在大多数实际情况下,K == N可以正常工作。

答案 1 :(得分:0)

@Holger假设K>=N给出了一个很好的答案。

您可以使用K<N的{​​{1}}和mark(int)方法将其扩展到reset()情况。

BufferedInputStream的参数是一行可以有多少个字节。

想法如下:

您只能将其中的mark行放在N中,而不是将所有TreeMap行放在K中。每当您将新行放入集合中并且该行已经“满”时,您就从其中逐出最小的一行。此外,您重置它来自的流。因此,当您再次读取它时,会弹出相同的数据。

您必须跟踪TreeSet中未保留的最大行,将其称为下限。一旦TreeSet中没有任何元素大于维持的下限,则再次扫描所有文件并重新填充集合。

我不确定这种方法是否最佳,但是应该可以。

此外,您必须知道BufferedInputStream的内部缓冲区至少为一行的大小,这样会占用大量内存,也许最好自己维护缓冲区