如何逐行复制大数据文件?


9

我有一个35GB的CSV文件。我想读取每一行,如果符合条件,则将该行写到新的CSV中。

try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("source.csv"))) {
    try (BufferedReader br = Files.newBufferedReader(Paths.get("target.csv"))) {
        br.lines().parallel()
            .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
            .forEach(line -> {
                writer.write(line + "\n");
        });
    }
}

这大约需要。7分钟 是否可以进一步加快该过程?


1
是的,您可以尝试不使用Java来执行此操作,而是直接通过Linux / Windows / etc来执行。操作系统。Java是经过解释的,因此使用它总是会产生开销。除此之外,不,我没有任何明显的方法可以加快速度,而且35GB的7分钟对我来说似乎很合理。
蒂姆·比格勒伊森

1
也许删除parallel使它更快?这不会使周围的线条混乱吗?
Thilo

1
BufferedWriter使用允许您设置缓冲区大小的构造函数来创建自己。也许更大(或更小)的缓冲区大小会有所不同。我会尝试将BufferedWriter缓冲区大小与主机操作系统缓冲区大小匹配。
亚伯

5
@TimBiegeleisen:“解释了Java”充其量是一种误导,而且几乎总是错误的。是的,对于某些优化,您可能需要离开JVM世界,但是用Java更快地执行此操作绝对是可行的。
约阿希姆·绍尔

1
您应该对应用程序进行概要分析,以查看是否有可以解决的热点。您将无法对原始IO做很多事情(默认的8192字节缓冲区还不错,因为涉及扇区大小等),但是可能(内部)正在发生某些事情,您可以一起工作。
卡亚曼

Answers:


4

如果可以选择,则可以使用GZipInputStream / GZipOutputStream最小化磁盘I / O。

我相信Files.newBufferedReader / Writer使用默认的缓冲区大小8 KB。您可以尝试使用更大的缓冲区。

转换为String,Unicode会减慢速度(并使用两倍的内存)。使用的UTF-8并不像StandardCharsets.ISO_8859_1那样简单。

最好的情况是,您可以在大多数情况下使用字节,并且仅将特定的CSV字段转换为字符串。

内存映射文件可能是最合适的。文件范围可能使用并行性,从而使文件分散。

try (FileChannel sourceChannel = new RandomAccessFile("source.csv","r").getChannel(); ...
MappedByteBuffer buf = sourceChannel.map(...);

这将成为很多代码,使代码正确(byte)'\n'无误,但又不会过于复杂。


读取字节的问题是,在现实世界中,我必须评估行的开头,特定字符的子字符串,然后仅将行的其余部分写入输出文件中。因此,我可能无法仅将行读取为字节?
membersound

我刚刚GZipInputStream + GZipOutputStream在虚拟磁盘上测试了完整的内存。表现差很多......
membersound

1
在Gzip上:则它不是慢速磁盘。是的,字节是一个选项:换行符,逗号,制表符,分号都可以作为字节处理,并且比作为字符串要快得多。字节从UTF-8到UTF-16字符从字符串到UTF-8到字节。
朱普·艾肯

1
随时间映射文件的不同部分。当达到极限时,只需MappedByteBuffer从上一个已知良好的位置开始创建一个新的(FileChannel.map花费多长时间)。
约阿希姆·绍尔

1
在2019年,无需使用new RandomAccessFile(…).getChannel()。只需使用FileChannel.open(…)
Holger

0

您可以尝试以下方法:

try (BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile), 1024 * 1024 * 64)) {
  try (BufferedReader br = new BufferedReader(new FileReader(sourceFile), 1024 * 1024 * 64)) {

我认为这将为您节省一到两分钟。通过指定缓冲区大小,可以在我的机器上大约4分钟内完成测试。

会更快吗?尝试这个:

final char[] cbuf = new char[1024 * 1024 * 128];

try (Writer writer = new FileWriter(targetFile)) {
  try (Reader br = new FileReader(sourceFile)) {
    int cnt = 0;
    while ((cnt = br.read(cbuf)) > 0) {
      // add your code to process/split the buffer into lines.
      writer.write(cbuf, 0, cnt);
    }
  }
}

这将为您节省三到四分钟。

如果还不够。(我想您可能会问这个问题,原因是您需要重复执行任务)。如果您想在一分钟甚至几秒钟内完成。那么您应该处理数据并将其保存到db中,然后由多台服务器处理任务。


举最后一个例子:我如何才能评估cbuf内容,只写出一部分?我是否必须在缓冲区满后重置缓冲区?(我怎么知道缓冲区已满?)
Membersound

0

感谢您的所有建议,我想到的最快的就是用交换作家BufferedOutputStream,这使性能提高了大约25%:

   try (BufferedReader reader = Files.newBufferedReader(Paths.get("sample.csv"))) {
        try (BufferedOutputStream writer = new BufferedOutputStream(Files.newOutputStream(Paths.get("target.csv")), 1024 * 16)) {
            reader.lines().parallel()
                    .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
                    .forEach(line -> {
                        writer.write((line + "\n").getBytes());
                    });
        }
    }

仍然BufferedReader表现比BufferedInputStream我的情况更好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.