将一个非常大的二进制文件逐步转换为Base64String
我需要帮助将非常大的二进制文件(ZIP文件)转换为Base64String并再次返回。 这些文件太大而无法一次性加载到内存中(它们会抛出OutOfMemoryExceptions),否则这将是一项简单的任务。 我不想单独处理ZIP文件的内容,我想处理整个ZIP文件。
问题:
我可以将整个ZIP文件(测试大小从1 MB到目前的800 MB)转换为Base64String,但是当我将其转换回来时,它已损坏。 新的ZIP文件是正确的大小,它被Windows和WinRAR / 7-Zip等识别为ZIP文件,我甚至可以查看ZIP文件内部并查看具有正确尺寸/属性的内容,但是当我试图从ZIP文件中提取,我得到:“错误:0x80004005”这是一般错误代码。
我不确定腐败发生的地点或原因。 我做了一些调查,我注意到以下几点:
如果您有一个大文本文件,您可以逐步将其转换为Base64String而不会出现问题。 如果在整个文件上调用Convert.ToBase64String
,则会产生: “abcdefghijklmnopqrstuvwx” ,然后在文件中调用两个部分将产生: “abcdefghijkl”和“mnopqrstuvwx” 。
不幸的是,如果文件是二进制文件,那么结果就不同了。 虽然整个文件可能会产生: “abcdefghijklmnopqrstuvwx” ,尝试将其分为两部分会产生类似: “oiweh87yakgb”和“kyckshfguywp” 。
有没有办法逐步基础64编码二进制文件,同时避免这种损坏?
我的代码:
private void ConvertLargeFile() { FileStream inputStream = new FileStream("C:\\Users\\test\\Desktop\\my.zip", FileMode.Open, FileAccess.Read); byte[] buffer = new byte[MultipleOfThree]; int bytesRead = inputStream.Read(buffer, 0, buffer.Length); while(bytesRead > 0) { byte[] secondaryBuffer = new byte[buffer.Length]; int secondaryBufferBytesRead = bytesRead; Array.Copy(buffer, secondaryBuffer, buffer.Length); bool isFinalChunk = false; Array.Clear(buffer, 0, buffer.Length); bytesRead = inputStream.Read(buffer, 0, buffer.Length); if(bytesRead == 0) { isFinalChunk = true; buffer = new byte[secondaryBufferBytesRead]; Array.Copy(secondaryBuffer, buffer, buffer.length); } String base64String = Convert.ToBase64String(isFinalChunk ? buffer : secondaryBuffer); File.AppendAllText("C:\\Users\\test\\Desktop\\Base64Zip", base64String); } inputStream.Dispose(); }
解码更加相同。 我使用上面的base64String
变量的大小(根据我测试的原始缓冲区大小而变化)作为解码的缓冲区大小。 然后,我调用Convert.FromBase64String()
并写入另一个文件名/路径,而不是Convert.ToBase64String()
。
编辑:
我急于减少代码(我将它重构为一个新项目,与其他处理分开,以消除不是问题的核心代码)我介绍了一个错误。 当应该使用buffer
应该在secondaryBuffer
对所有迭代执行base 64转换,保存最后一个(由isFinalChunk
标识)。 我已经纠正了上面的代码。
编辑#2:
谢谢大家的意见/反馈。 在纠正错误后(参见上面的编辑),我重新测试了我的代码,现在它实际上正在运行。 我打算测试并实施@rene的解决方案,因为它似乎是最好的,但我认为我应该让每个人都知道我的发现。
基于Wiktor Zychla 博客中显示的代码,以下代码有效。 如Ivan Stoev所指出的,在Convert.ToBase64String的备注部分中指出了相同的解决方案
// using System.Security.Cryptography private void ConvertLargeFile() { //encode var filein= @"C:\Users\test\Desktop\my.zip"; var fileout = @"C:\Users\test\Desktop\Base64Zip"; using (FileStream fs = File.Open(fileout, FileMode.Create)) using (var cs=new CryptoStream(fs, new ToBase64Transform(), CryptoStreamMode.Write)) using(var fi =File.Open(filein, FileMode.Open)) { fi.CopyTo(cs); } // the zip file is now stored in base64zip // and decode using (FileStream f64 = File.Open(fileout, FileMode.Open) ) using (var cs=new CryptoStream(f64, new FromBase64Transform(), CryptoStreamMode.Read ) ) using(var fo =File.Open(filein +".orig", FileMode.Create)) { cs.CopyTo(fo); } // the original file is in my.zip.orig // use the commandlinetool // fc my.zip my.zip.orig // to verify that the start file and the encoded and decoded file // are the same }
该代码使用System.Security.Cryptography命名空间中的标准类,并使用CryptoStream
和FromBase64Transform
及其对应的ToBase64Transform
您可以通过将offset和length传递给Convert.ToBase64String
来避免使用辅助缓冲区,如下所示:
private void ConvertLargeFile() { using (var inputStream = new FileStream("C:\\Users\\test\\Desktop\\my.zip", FileMode.Open, FileAccess.Read)) { byte[] buffer = new byte[MultipleOfThree]; int bytesRead = inputStream.Read(buffer, 0, buffer.Length); while(bytesRead > 0) { String base64String = Convert.ToBase64String(buffer, 0, bytesRead); File.AppendAllText("C:\\Users\\test\\Desktop\\Base64Zip", base64String); bytesRead = inputStream.Read(buffer, 0, buffer.Length); } } }
以上应该有效,但我认为Rene的答案实际上是更好的解决方案。
使用此代码:
public void ConvertLargeFile(string source , string destination) { using (FileStream inputStream = new FileStream(source, FileMode.Open, FileAccess.Read)) { int buffer_size = 30000; //or any multiple of 3 byte[] buffer = new byte[buffer_size]; int bytesRead = inputStream.Read(buffer, 0, buffer.Length); while (bytesRead > 0) { byte[] buffer2 = buffer; if(bytesRead < buffer_size) { buffer2 = new byte[bytesRead]; Buffer.BlockCopy(buffer, 0, buffer2, 0, bytesRead); } string base64String = System.Convert.ToBase64String(buffer2); File.AppendAllText(destination, base64String); bytesRead = inputStream.Read(buffer, 0, buffer.Length); } } }