在C#中,最快的连接三个文件的方法是什么?

24
我需要用C#串联三个文件,包括一个头文件、内容文件和一个脚注文件,但我希望能够以最酷的方式来完成它。
"酷"指的是代码尽可能地小或者非汇编代码运行速度非常快。

“酷炫”的代码听起来很像“聪明”的代码。这可能是一个有趣的新奇之处,但如果它更难阅读、维护、调试、测试等,从长远来看就不值得了。 - Brian Ensink
2
C# 中没有汇编代码... - BBetances
是的,但您可以在C#程序中使用汇编代码。 - Bobby Bruckovnic
6
代码不会成为瓶颈,相反,与硬盘通信的方式将成为瓶颈。不要写酷炫的代码,写易于阅读和维护的代码才更酷! - Lasse V. Karlsen
“真小的代码或者真快的执行速度”——我是不是唯一一个觉得这个听起来不对的人? - Paul Draper
@BobbyBruckovnic 尝试使用多线程,创建2个线程 - 当第1个线程读取第2个文件时,您的第2个线程可以并行地写入第1个文件数据。 - Kartik Maheshwari
6个回答

42

我支持Mehrdad Afshari使用与System.IO.Stream.CopyTo相同的代码。 但我仍然会想知道,为什么他没有使用相同的函数而是重新编写它的实现。

string[] srcFileNames = { "file1.txt", "file2.txt", "file3.txt" };
string destFileName = "destFile.txt";

using (Stream destStream = File.OpenWrite(destFileName))
{
    foreach (string srcFileName in srcFileNames)
    {
        using (Stream srcStream = File.OpenRead(srcFileName))
        {
            srcStream.CopyTo(destStream);
        }
    }
}

根据反汇编器(ILSpy)显示,默认缓冲区大小为4096。但是,CopyTo函数有一个重载,允许在不满意4096字节的情况下指定缓冲区大小。


非常好的答案。这正是我所需要的。 - STLDev
我需要在每个文件结束后关闭 srcStream 吗? - rraallvv
@rraallvv 当您使用using时,就像OP在他的答案中写的那样,它会自动清理。 - bugybunny

27
void CopyStream(Stream destination, Stream source) {
   int count;
   byte[] buffer = new byte[BUFFER_SIZE];
   while( (count = source.Read(buffer, 0, buffer.Length)) > 0)
       destination.Write(buffer, 0, count);
}


CopyStream(outputFileStream, fileStream1);
CopyStream(outputFileStream, fileStream2);
CopyStream(outputFileStream, fileStream3);

2
我不认为这很聪明。什么是一个好的BUFFER_SIZE?没有人知道。与File.ReadAllText(a) + File.ReadAllText(b) + File.ReadAllText(c)相比,这对我来说看起来像是过早优化。 - nes1983
2
取决于你的文件大小。使用ReadAllText方法连接几百兆字节的文件可能不太合适。 - gimpf
@Nico,智能和简单是相对的。 - Mehrdad Afshari
2
顺便说一句,除非你正在处理文本文件,否则ReadAllText绝对不是正确的选择,因为在将它们读入字符串时可能会破坏文件内容。性能方面暂且不论。 - Mehrdad Afshari
你可能需要这种较低级别的方法的情况是,当你想向用户反馈进度时。 - Giles
显示剩余9条评论

7

如果您的文件是文本且不是很大,那么可以考虑使用简单明了的代码。我建议使用以下代码。

File.ReadAllText("file1") + File.ReadAllText("file2") + File.ReadAllText("file3");

如果您的文件是大型文本文件,并且您使用的是Framework 4.0,则可以使用File.ReadLines来避免缓冲整个文件。
File.WriteAllLines("out", new[] { "file1", "file2", "file3" }.SelectMany(File.ReadLines));

如果您的文件是二进制的,请参考Mehrdad的回答


1
免责声明:此代码不适用于大文件。请参考Mehrdad Afshari的代码以获取更多信息。 - Jimmy
2
这看起来需要大量的内存来将所有三个文件作为字符串保存在内存中,更不用说通过添加前两个文件得到的中间字符串对象和通过添加最后一个文件创建的最终字符串了。 - Brian Ensink
吉米,你在免责声明方面比我快了! :) - Brian Ensink
  1. 连接字符串不是一个好的做法。你应该使用stringbuilder。
  2. 这不是二进制文件的好解决方案。
- TcKs
2
@TcKs:1)在此处进行的行内连接是通过一次string.concat(a,b,c)操作完成的,开销比stringbuilder低。 2)我假设“头/内容/页脚”文件是文本文件。 - Jimmy
请注意,这将重写您的文件为UTF-8格式,这可能不是您想要的。用户664769提供的解决方案使用File.OpenRead操作字节,因此是文件的真正连接,而不必担心编码问题。 - aolszowka

6
另一种方法是让操作系统替您完成:
ProcessStartInfo psi = new ProcessStartInfo("cmd.exe", 
        String.Format(@" /c copy {0} + {1} + {2} {3}", 
            file1, file2, file3, dest));
psi.UseShellExecute = false;
Process process = Process.Start(psi);
process.WaitForExit();

4
除非你在裸机上运行,否则无论如何都要让操作系统去做。但只有在绝对必要时才应该使用外壳程序。请注意不要改变原意,我会尽力使翻译更加通俗易懂。 - Joey

3
你是指3个文本文件吗?结果需要再次生成一个文件吗?
可以尝试这样描述:
string contents1 = File.ReadAllText(filename1);
string contents2 = File.ReadAllText(filename2);
string contents3 = File.ReadAllText(filename3);

File.WriteAllText(outputFileName, contents1 + contents2 + contents3);

当然,有了StringBuilder和一些额外的技巧,您可以轻松地扩展它以处理任意数量的输入文件 :-)
干杯

2
这将强制将所有文件的内容加载到内存中。如果您有大文件,这非常低效,因为您可能会强制对象进入大对象堆。使用缓冲区将避免垃圾回收的需要,并且几乎肯定会更高效。 - brianfeucht

1

如果您在Win32环境中,最有效的解决方案可能是使用Win32 API函数“WriteFile”。VB 6中有一个示例,但在C#中重新编写它并不困难。


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