2015-09-21 12 views
7

Ich brauche Hilfe beim Konvertieren einer sehr großen Binärdatei (ZIP-Datei) in einen Base64String und wieder zurück. Die Dateien sind zu groß, um sie alle gleichzeitig in den Speicher zu laden (sie werfen OutOfMemoryExceptions aus), sonst wäre dies eine einfache Aufgabe. Ich möchte den Inhalt der ZIP-Datei nicht einzeln verarbeiten, sondern die gesamte ZIP-Datei bearbeiten.Konvertieren Sie eine sehr große Binärdatei in eine Base64String inkrementell

Das Problem:

ich die gesamte ZIP-Datei umwandeln kann (Testgrößen von 1 MB bis 800 MB derzeit variieren) zu Base64String, aber wenn ich es zurück konvertieren, wird es beschädigt. Die neue ZIP-Datei hat die richtige Größe, sie wird von Windows und WinRAR/7-Zip usw. als ZIP-Datei erkannt, und ich kann sogar in die ZIP-Datei schauen und den Inhalt mit den richtigen Größen/Eigenschaften sehen, aber wann Ich versuche, aus der ZIP-Datei zu extrahieren, bekomme ich: "Fehler: 0x80004005" das ist ein allgemeiner Fehlercode.

Ich bin mir nicht sicher, wo oder warum die Korruption passiert. Ich habe einige Nachforschungen angestellt, und mir ist Folgendes aufgefallen:

Wenn Sie eine große Textdatei haben, können Sie sie inkrementell ohne Probleme in Base64String konvertieren. Wenn Convert.ToBase64String auf die gesamte Datei aufrufen ergab: „abcdefghijklmnopqrstuvwx“, nannte es dann auf die Datei in zwei Stücke ergäbe: „abcdefghijkl“ und „mnopqrstuvwx“.

Wenn die Datei eine Binärdatei ist, ist das Ergebnis leider anders. Während die gesamte Datei könnte ergeben: „abcdefghijklmnopqrstuvwx“, versucht, dies in zwei Stücke zu verarbeiten etwas wie ergeben würde: „oiweh87yakgb“ und „kyckshfguywp“.

Gibt es eine Möglichkeit, inkrementell Basis 64 codieren eine Binärdatei, während diese Korruption zu vermeiden?

Mein Code:

 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(); 
     } 

Die Decodierung mehr der gleiche ist. Ich benutze die Größe der base64String Variable oben (die abhängig von der ursprünglichen Puffergröße, mit der ich teste), als die Puffergröße für die Decodierung. Dann, statt Convert.ToBase64String(), rufe ich Convert.FromBase64String() an und schreibe in einen anderen Dateinamen/Pfad.

EDIT:

In meiner Eile, den Code zu reduzieren (ich es in ein neues Projekt Refactoring, getrennt von anderem Verarbeitungscode zu eliminieren, die nicht von zentraler Bedeutung für die Frage ist) Ich habe einen Fehler eingeführt. Die Basis-64-Konvertierung sollte auf dem secondaryBuffer für alle Iterationen durchgeführt werden, speichern Sie die letzte (Identified von isFinalChunk), wenn buffer verwendet werden soll. Ich habe den obigen Code korrigiert.

EDIT # 2:

Vielen Dank für Ihre Kommentare/Feedback. Nachdem ich den Fehler korrigiert habe (siehe oben), habe ich meinen Code erneut getestet und er funktioniert jetzt. Ich beabsichtige, die Lösung von @rene zu testen und zu implementieren, da sie die beste Lösung zu sein scheint, aber ich dachte, dass ich auch alle meine Entdeckung wissen lassen sollte.

+0

Was machst du mit dem sekundären Puffer und 'isFinalChunk'? Es sieht so aus, als würdest du 'ToBase64String' in einem gelöschten Puffer aufrufen, außer es ist der letzte Teil. – Blorgbeard

+3

Problem kann in Code sein, der Dateien von Base64 in Binärdatei zurück konvertiert. Liest du Charakter in Stücke von vier oder Mulitple of Four? – Vova

+0

@Blorgbeard - Ich verwende den secondaryBuffer, um den Inhalt des ersten/aktuellen Lesevorgangs aus der Datei zu halten. Dann lese ich erneut und suche nach einer Rückkehr von '0', um anzuzeigen, dass ich den letzten Block verarbeitet habe. Der endgültige Chunk wird so skaliert, dass er nur groß genug ist, um die Daten zu speichern, die gerade codiert werden. Z.B. - Wenn der Puffer auf 600.000 gesetzt wurde, aber der letzte Lesevorgang 1000 Byte lang ist, muss kein Byte [] mit 600.000 Elementen übergeben werden. Wenn ich nicht auf dem letzten Chunk bin, dann verarbeite ich stattdessen 'secondaryBuffer', der die erforderlichen Daten enthält. – CaptainCobol

Antwort

10

Basierend auf den Code in the blog von Wiktor Zychla der folgende Code funktioniert gezeigt. Die gleiche Lösung in den Erläuterungen Abschnitt Convert.ToBase64String angezeigt wird, wie durch Ivan Stoev

// 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 
} 

Der Code verwendet Standardklassen in System.Security.Cryptography Namespace aufgezeigt und verwendet eine CryptoStream und die FromBase64Transform und sein Pendant ToBase64Transform

+3

Das ist in der Tat die richtige Antwort! Die MSDN-Dokumentation für "Convert.ToBase64String" -Methode (https://msdn.microsoft.com/en-us/library/s70ad5f6(v=vs.100).aspx) enthält ** Wichtig ** Hinweis in den ** Bemerkungen ** Sektion, die genau das empfiehlt. –

+0

@rene - Ich kannte diese Transformationen nicht. Das sieht nach dem aus, was ich von Anfang an gesucht habe. Werde es definitiv versuchen. – CaptainCobol

8

Das kann durch Offset und Länge zu Convert.ToBase64String, wie dies unter Verwendung eines sekundären Puffers vermeiden:

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);   
     } 
    } 
} 

Die oben sollte funktionieren, aber ich denke, Rene's answer ist eigentlich die bessere Lösung.

+0

Löscht 'stream.Read' den Eingangspuffer vor dem Lesen? Wenn Sie 3 und 2 lesen, hat das letzte Byte einen alten Wert? –

+3

@DaveZych: Es ist nicht klar, aber das spielt keine Rolle, da Sie "Offset" und "Länge" an die "Convert.ToBase64String" -Methode übergeben –

+0

@DaveZych, Sie sind richtig, und das ist, warum ich clear der Puffer vor jedem neuen Lesevorgang. – CaptainCobol

1

Mit diesem Code:

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); 

     } 
    } 
} 
+0

Buffer.BlockCopy ist in diesem Szenario nicht sicher. Ich benutzte es ursprünglich, aber ich fand, dass meine Kopienarrays halb mit Nullen gefüllt waren. Siehe: http://StackOverflow.com/a/1390023/4659717 – CaptainCobol

+0

Warum ist es nicht sicher? Ich bin mir sicher, dass es so ist. Wie auch immer, ich habe bemerkt, dass die Antwort von Blorgbeard tatsächlich besser ist, es macht dasselbe wie ich, außer dass es nicht Buffer.BlockCopy verwendet, sondern eine andere Überladung der ToBase64String-Methode. –

+0

Wenn Sie dem angegebenen Link folgen, erklärt MusiGenesis es gut. Meine Arrays waren halb ausgefüllt - mit Inhalt und halb voll mit Nullen. Buffer.BlockCopy-Parameter sind Byte-basiert und nicht indexbasiert. – CaptainCobol

Verwandte Themen