如何最好地合并大文件?

4
我需要合并数千个大文件(每个文件约200MB)。我想知道最好的合并方法是什么。行将被有条件地复制到合并后的文件中。可以使用File.AppendAllLines或Stream.CopyTo吗?
使用File.AppendAllLines
for (int i = 0; i < countryFiles.Length; i++){
   string srcFileName = countryFiles[i];
   string[] countryExtractLines = File.ReadAllLines(srcFileName);  
   File.AppendAllLines(actualMergedFileName, countryExtractLines);
}

使用 Stream.CopyTo
using (Stream destStream = File.OpenWrite(actualMergedFileName)){
  foreach (string srcFileName in countryFiles){
    using (Stream srcStream = File.OpenRead(srcFileName)){
        srcStream.CopyTo(destStream);
    }
  }
}

1
使用 StreamWriter 创建一个新文件,使用 StreamReader 读取您想要合并的所有文件,并写入您的写入器。 - Camo
我怀疑很多人会回答:“试一下,比较一下两者的差异”。 - Wai Ha Lee
1
我相信你会想要一个 StreamReader 并逐行迭代文件,因为这样它不会一次性将所有内容存储在内存中。 - sab669
3
您是否只想追加文件?如果是,请使用Stream.CopyTo(),但使用File.Open("filename", FileMode.Append)打开要追加的现有文件。如果使用File.OpenWrite(),会出现严重错误。 - Matthew Watson
1
那么你肯定不想使用ReadAllLines,因为正如sab669所提到的那样,这会将200MB的数据加载到内存中。 - Zdeněk Jelínek
显示剩余2条评论
3个回答

4
您可以一个接一个地编写文件。例如:
static void MergingFiles(string outputFile, params string[] inputTxtDocs)
{
    using (Stream outputStream = File.OpenWrite(outputFile))
    {
      foreach (string inputFile in inputTxtDocs)
      {
        using (Stream inputStream = File.OpenRead(inputFile))
        {
          inputStream.CopyTo(outputStream);
        }
      }
    }
}

在我看来,上面的代码非常高效,因为Stream.CopyTo()方法具有非常简单的算法,所以该方法非常有效。反编译器将其核心呈现如下:

private void InternalCopyTo(Stream destination, int bufferSize)
{
  int num;
  byte[] buffer = new byte[bufferSize];
  while ((num = this.Read(buffer, 0, buffer.Length)) != 0)
  {
     destination.Write(buffer, 0, num);
  }
}

1
这对我在大批量文件上的操作非常高效。 - Norman H

3

sab669的答案是正确的,您需要使用StreamReader,然后循环读取文件的每一行......不过我建议您单独为每个文件编写代码,否则,如果有多个200MB的文件,您很快就会耗尽内存。

例如:

foreach(File f in files)
{
    List<String> lines = new List<String>();
    string line;
    int cnt = 0;
    using(StreamReader reader = new StreamReader(f)) {
        while((line = reader.ReadLine()) != null) {
            // TODO : Put your conditions in here
            lines.Add(line);
            cnt++;
        }
    }
    f.Close();
    // TODO : Append your lines here using StreamWriter
}

为了更好地复制文件,最好使用字节缓冲算法。我们这里根本不需要使用字符串。 - gabba
1
根据原帖,@gabba需要“有条件地操作每一行”。 - Matthew Watson

2
假设您有一个条件,必须对要附加到另一个文件的每一行(即谓词)都成立。您可以按照以下方式有效地处理它:
var filteredLines = 
    File.ReadLines("MySourceFileName")
    .Where(line => line.Contains("Target")); // Put your own condition here.

File.AppendAllLines("MyDestinationFileName", filteredLines);

这种方法可以扩展到多个文件,并避免将整个文件加载到内存中。

如果您想替换内容而不是将所有行附加到文件中,则可以执行以下操作:

File.WriteAllLines("MyDestinationFileName", filteredLines);

替代

File.AppendAllLines("MyDestinationFileName", filteredLines);

请注意,这些方法还有一些重载版本,允许您指定编码(如果您没有使用UTF8)。
最后,不要被不一致的方法命名所迷惑。File.ReadLines()不会将所有行都读入内存,但是File.ReadAllLines()会。然而,File.WriteAllLines()并不会缓冲所有行到内存中,也不希望它们都缓存在内存中;它使用IEnumerable<string>作为输入。

谢谢。刚刚从MSDN上读到:ReadLines和ReadAllLines方法的区别如下:当您使用ReadLines时,您可以在整个集合返回之前开始枚举字符串集合;当您使用ReadAllLines时,必须等待整个字符串数组返回后才能访问该数组。因此,在处理非常大的文件时,ReadLines可能更有效。 - LUIS PEREIRA
@LUISPEREIRA 嗯,所以我建议使用这种简单的方法。还要注意我的最后一段关于微软不一致的命名! - Matthew Watson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接