如何在C#中编写超快的文件stream代码?
我必须将一个巨大的文件分成许多小文件。 每个目标文件都由偏移量和长度定义为字节数。 我使用下面的代码:
private void copy(string srcFile, string dstFile, int offset, int length) { BinaryReader reader = new BinaryReader(File.OpenRead(srcFile)); reader.BaseStream.Seek(offset, SeekOrigin.Begin); byte[] buffer = reader.ReadBytes(length); BinaryWriter writer = new BinaryWriter(File.OpenWrite(dstFile)); writer.Write(buffer); }
考虑到我不得不把这个函数调用大约10万次,速度非常慢。
- 有没有办法让作家直接连接到读者? (也就是说,实际上没有将内容加载到内存中的缓冲区中。)
我不相信.NET中有任何东西可以复制文件的一部分,而不用在内存中缓冲它。 然而,这让我觉得这是无效的,因为它需要打开input文件并多次寻找。 如果你只是分割文件,为什么不打开一次input文件,然后写下如下所示:
public static void CopySection(Stream input, string targetFile, int length) { byte[] buffer = new byte[8192]; using (Stream output = File.OpenWrite(targetFile)) { int bytesRead = 1; // This will finish silently if we couldn't read "length" bytes. // An alternative would be to throw an exception while (length > 0 && bytesRead > 0) { bytesRead = input.Read(buffer, 0, Math.Min(length, buffer.Length)); output.Write(buffer, 0, bytesRead); length -= bytesRead; } } }
这在每次调用时创build一个缓冲区的效率都很低 – 您可能需要创build一次缓冲区,并将其传递给方法:
public static void CopySection(Stream input, string targetFile, int length, byte[] buffer) { using (Stream output = File.OpenWrite(targetFile)) { int bytesRead = 1; // This will finish silently if we couldn't read "length" bytes. // An alternative would be to throw an exception while (length > 0 && bytesRead > 0) { bytesRead = input.Read(buffer, 0, Math.Min(length, buffer.Length)); output.Write(buffer, 0, bytesRead); length -= bytesRead; } } }
请注意,这也会closures输出stream(由于using语句),您的原始代码不会。
重要的一点是,这将更有效地使用操作系统文件缓冲,因为您重复使用相同的inputstream,而不是在开始时重新打开文件,然后查找。
我认为这会明显更快,但显然你需要尝试看看…
当然,这个假设是连续的。 如果您需要跳过该文件的位,可以从该方法之外执行该操作。 另外,如果你正在编写非常小的文件,你可能也想优化这种情况 – 最简单的方法可能是引入一个包装inputstream的BufferedStream
。
从C#执行文件I / O的最快方法是使用Windows ReadFile和WriteFile函数。 我已经写了一个封装了这个function的C#类,以及一个查看不同的I / O方法的基准程序,包括BinaryReader和BinaryWriter。 看我的博客文章:
http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/
length
多大? 你可能会更好地重新使用一个固定大小(中等大,但不是淫秽)的缓冲区,并忘记BinaryReader
…只是使用Stream.Read
和Stream.Write
。
(编辑)如下所示:
private static void copy(string srcFile, string dstFile, int offset, int length, byte[] buffer) { using(Stream inStream = File.OpenRead(srcFile)) using (Stream outStream = File.OpenWrite(dstFile)) { inStream.Seek(offset, SeekOrigin.Begin); int bufferLength = buffer.Length, bytesRead; while (length > bufferLength && (bytesRead = inStream.Read(buffer, 0, bufferLength)) > 0) { outStream.Write(buffer, 0, bytesRead); length -= bytesRead; } while (length > 0 && (bytesRead = inStream.Read(buffer, 0, length)) > 0) { outStream.Write(buffer, 0, bytesRead); length -= bytesRead; } } }
每次复制时都不要重新打开源文件,最好打开一次,并将生成的BinaryReader传递给复制函数。 另外,如果你命令你的search,它可能会有所帮助,所以你不要在文件里跳大。
如果长度不是太大,也可以尝试通过将彼此靠近的偏移量进行分组,并读取您需要的整个块来对多个复制调用进行分组,例如:
offset = 1234, length = 34 offset = 1300, length = 40 offset = 1350, length = 1000
可以分组为一个读取:
offset = 1234, length = 1074
那么你只需要在你的缓冲区中“寻找”,并且可以从那里写入三个新文件,而不必再次读取。
您是否考虑过使用CCR,因为您正在编写单独的文件,您可以并行执行所有操作(读取和写入),并且CCR使得执行此操作变得非常简单。
static void Main(string[] args) { Dispatcher dp = new Dispatcher(); DispatcherQueue dq = new DispatcherQueue("DQ", dp); Port<long> offsetPort = new Port<long>(); Arbiter.Activate(dq, Arbiter.Receive<long>(true, offsetPort, new Handler<long>(Split))); FileStream fs = File.Open(file_path, FileMode.Open); long size = fs.Length; fs.Dispose(); for (long i = 0; i < size; i += split_size) { offsetPort.Post(i); } } private static void Split(long offset) { FileStream reader = new FileStream(file_path, FileMode.Open, FileAccess.Read); reader.Seek(offset, SeekOrigin.Begin); long toRead = 0; if (offset + split_size <= reader.Length) toRead = split_size; else toRead = reader.Length - offset; byte[] buff = new byte[toRead]; reader.Read(buff, 0, (int)toRead); reader.Dispose(); File.WriteAllBytes("c:\\out" + offset + ".txt", buff); }
此代码将偏移量过帐到CCR端口,从而导致创build线程以执行Split方法中的代码。 这会导致您多次打开该文件,但无需同步。 你可以提高记忆效率,但是你必须牺牲速度。
我build议的第一件事就是进行测量。 你在哪里失去时间? 它是在读,还是在写?
超过100,000次访问(总结时间):分配缓冲区数组花费多less时间? 花了多less时间打开文件进行读取(每次都是同一个文件?)在读写操作上花了多less时间?
如果你没有对文件进行任何types的转换,你需要一个BinaryWriter,或者你可以使用文件stream写入? (尝试一下,你有相同的输出吗?这是否节省了时间?)
使用FileStream + StreamWriter我知道可以在很短的时间内创build大量的文件(小于1分30秒)。 我使用这种技术从一个文件生成了三个总共700多兆字节的文件。
你使用的代码的主要问题是你每次打开一个文件。 这是创build文件I / O开销。
如果您知道您将提前生成的文件的名称,则可以将File.OpenWrite提取为单独的方法; 它会提高速度。 没有看到确定你如何分割文件的代码,我不认为你会变得更快。
没有人build议穿线? 编写较小的文件看起来像是线程有用的教科书示例。 设置一堆线程来创build较小的文件。 这样,你可以并行创build它们,而不需要等待每一个完成。 我的假设是,创build文件(磁盘操作)将花费更长的时间比分裂数据。 当然你应该首先validation一个顺序的方法是不够的。
(备查。)
很可能最快的方法是使用内存映射文件(主要是复制内存,操作系统通过分页/内存pipe理来处理文件的读/写操作)。
内存映射的文件在.NET 4.0托pipe代码中受支持。
但是如上所述,您需要进行configuration文件,并期望切换到本机代码以获得最佳性能。