以下回答->
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)
致谢
答案 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
的内部缓冲区至少为一行的大小,这样会占用大量内存,也许最好自己维护缓冲区