合并大文件而不将整个文件加载到内存中?

Posted

技术标签:

【中文标题】合并大文件而不将整个文件加载到内存中?【英文标题】:Merge huge files without loading whole file into memory? 【发布时间】:2014-10-22 04:40:33 【问题描述】:

我想将包含字符串的大文件合并到一个文件中,并尝试使用 nio2。我不想将整个文件加载到内存中,所以我用 BufferedReader 进行了尝试:

public void mergeFiles(filesToBeMerged) throws IOException

Path mergedFile = Paths.get("mergedFile");
Files.createFile(mergedFile);

List<Path> _filesToBeMerged = filesToBeMerged;

try (BufferedWriter writer = Files.newBufferedWriter(mergedFile,StandardOpenOption.APPEND)) 
        for (Path file : _filesToBeMerged) 
// this does not work as write()-method does not accept a BufferedReader
            writer.append(Files.newBufferedReader(file));
        
     catch (IOException e) 
        System.err.println(e);
    


我用这个试过了,这行得通,但是,字符串的格式(例如,新行等不会复制到合并文件中):

...
try (BufferedWriter writer = Files.newBufferedWriter(mergedFile,StandardOpenOption.APPEND)) 
        for (Path file : _filesToBeMerged) 
//              writer.write(Files.newBufferedReader(file));
            String line = null;


BufferedReader reader = Files.newBufferedReader(file);
            while ((line = reader.readLine()) != null) 
                    writer.append(line);
                    writer.append(System.lineSeparator());
             
reader.close();
        
     catch (IOException e) 
        System.err.println(e);
    
...

如何在不将整个文件加载到内存的情况下将大文件与 NIO2 合并?

【问题讨论】:

【参考方案1】:

如果您想有效地合并两个或多个文件,您应该问自己,到底为什么要使用基于ReaderWriterchar 来执行该任务。

通过使用这些类,您可以将文件的字节转换为字符,从系统的默认编码转换为 unicode,然后从 unicode 转换为系统的默认编码。这意味着程序必须对整个文件进行两次数据转换。

顺便说一下,BufferedReaderBufferedWriter 绝不是 NIO2 工件。这些类从 Java 的第一个版本开始就存在。

当您通过真正的 NIO 函数使用逐字节复制时,可以在不被 Java 应用程序接触的情况下传输文件,在最好的情况下,传输将直接在文件系统的缓冲区中执行:

import static java.nio.file.StandardOpenOption.*;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;

public class MergeFiles

  public static void main(String[] arg) throws IOException 
    if(arg.length<2) 
      System.err.println("Syntax: infiles... outfile");
      System.exit(1);
    
    Path outFile=Paths.get(arg[arg.length-1]);
    System.out.println("TO "+outFile);
    try(FileChannel out=FileChannel.open(outFile, CREATE, WRITE)) 
      for(int ix=0, n=arg.length-1; ix<n; ix++) 
        Path inFile=Paths.get(arg[ix]);
        System.out.println(inFile+"...");
        try(FileChannel in=FileChannel.open(inFile, READ)) 
          for(long p=0, l=in.size(); p<l; )
            p+=in.transferTo(p, l-p, out);
        
      
    
    System.out.println("DONE.");
  

【讨论】:

哇,这个解决方案真的很棒——而且源代码很短。谢谢!您知道基于 nio2 的解决方案,用于将大文件拆分为一组小文件吗?实际上,我正在使用类似 todayguesswhat.blogspot.de/2014/05/…. @nimo23: 好吧,我想,当你尝试理解我的答案的代码时,尤其是FileChannel.transferTo 所做的事情,你会意识到拆分解决方案的样子(阅读:非常相似)。如果您在实施时遇到困难,可以提出一个新问题。 好的,我自己试试,这里会提供解决方案! 好的,我已经发布了解决方案:***.com/questions/25553673/…。我找不到使用 nio2 的解决方案,因为使用 nio2 拆分文件的大小只能通过文件大小来减小。但是,我想按行号拆分文本文件。您是否找到了使用 nio2 的 splitTextFiles()-Method 的(更好的)解决方案?【参考方案2】:

readLine() 不会产生行尾(“\n”或“\r\n”)。那是错误。

while ((line = reader.readLine()) != null) 
    writer.write(line);
    writer.write("\r\n"); // Windows

您也可以忽略对(可能不同)行尾的过滤,并使用

try (OutputStream out = new FileOutputStream(file);
    for (Path source : filesToBeMerged) 
        Files.copy(path, out);
        out.write("\r\n".getBytes(StandardCharsets.US_ASCII));
    

这会显式写入换行符,以防最后一行不以换行符结尾。

在文件开头将文本标记为 UTF-8/UTF-16LE/UTF-16BE 的可选的、丑陋的 Unicode BOM 字符可能仍然存在问题。

【讨论】:

【参考方案3】:

Files.newBufferedReader(file).readLine()

你每次都创建一个新的缓冲区,它总是在第一行重置。

替换为

BufferedReader reader = Files.newBufferedReader(file);
while ((line = reader.readLine()) != null) 
  writer.write(line);

.close() 完成后的读者。

【讨论】:

谢谢,我在源代码中做了修改。你知道,我怎样才能将合并文件的格式保留为“mergedFile”-File?例如,合并的文件有回车或空行。使用上述方法时,所有这些都不会复制到“mergedFile”中。 不确定您的意思,但您可以使用 writer.write(System.lineSeparator()); 手动追加换行符; 我想知道哪个性能更好。以上解决方案或programcreek.com/2012/09/merge-files-in-java中的解决方案。你知道哪一个性能更好? @nimo23 为它写一个测试。你有一个大文件,所以执行复制几次并检查一种方法花费了多少时间,另一种方法花费了多少时间。

以上是关于合并大文件而不将整个文件加载到内存中?的主要内容,如果未能解决你的问题,请参考以下文章

从文本文件中读取第一行而不将整个文本文件加载到内存中

Actionscript 3,只能读取文件的一部分而不将整个文件加载到内存中

python 以块的形式读取文件而不将整个文件加载到内存中。

如何使用 Java 裁剪图像而不将其加载到内存中

如何读取大的avro文件,并将整个文件加载到内存中。

裁剪图像而不加载到内存中