2015-09-21 8 views
7

Ho bisogno di aiuto per convertire un file binario MOLTO GRANDE (file ZIP) in Base64String e viceversa. I file sono troppo grandi per essere caricati in memoria tutti in una volta (gettano OutOfMemoryExceptions) altrimenti questo sarebbe un compito semplice. Non voglio elaborare i contenuti del file ZIP singolarmente, voglio elaborare l'intero file ZIP.Convertire un file binario MOLTO GRANDE in Base64String in modo incrementale

Il problema:

posso convertire l'intero file ZIP (dimensioni di prova variano da 1 MB a 800 MB al momento) per Base64String, ma quando ho riconvertirlo, è danneggiato. Il nuovo file ZIP ha le dimensioni corrette, è riconosciuto come file ZIP da Windows e WinRAR/7-Zip, ecc., E posso persino guardare all'interno del file ZIP e vedere i contenuti con le dimensioni/proprietà corrette, ma quando Tento di estrarre dal file ZIP, ottengo: "Errore: 0x80004005" che è un codice di errore generale.

Non sono sicuro di dove o perché avvenga la corruzione. Ho effettuato alcune ricerche e ho notato quanto segue:

Se si dispone di un file di testo di grandi dimensioni, è possibile convertirlo in Base64String in modo incrementale senza problemi. Se chiamando Convert.ToBase64String su tutto il file prodotto: "abcdefghijklmnopqrstuvwx", quindi chiamando sul file in due pezzi sarebbe resa: "abcdefghijkl" e "mnopqrstuvwx".

Sfortunatamente, se il file è un file binario, il risultato è diverso. Mentre l'intero file potrebbe cedere: "abcdefghijklmnopqrstuvwx", cercando di elaborare questo in due pezzi produrrebbe qualcosa come: "oiweh87yakgb" e "kyckshfguywp".

C'è un modo per codificare in modo incrementale 64 codificare un file binario evitando questo danneggiamento?

Il mio codice:

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

La decodifica è più la stessa. Uso la dimensione della variabile base64String qui sopra (che varia in base alla dimensione del buffer originale con cui eseguo il test), come dimensione del buffer per la decodifica. Quindi, invece di Convert.ToBase64String(), chiamo Convert.FromBase64String() e scrivo in un nome/percorso diverso.

EDIT:

Nella mia fretta di ridurre il codice (ho refactoring in un nuovo progetto, separato da altre lavorazioni per eliminare codice che non è al centro del problema) ho introdotto un bug. La conversione di base 64 deve essere eseguita su secondaryBuffer per tutte le iterazioni con salvataggio dell'ultima (Identificato da isFinalChunk), quando deve essere utilizzato buffer. Ho corretto il codice sopra.

EDIT # 2:

Grazie a tutti per i vostri commenti/feedback. Dopo aver corretto il bug (vedere la modifica sopra), ho riesaminato il mio codice e attualmente funziona. Intendo testare e implementare la soluzione di @ rene in quanto sembra essere la migliore, ma ho pensato che avrei dovuto far sapere a tutti anche della mia scoperta.

+0

Cosa stai facendo con il buffer secondario e 'isFinalChunk'? Sembra che tu stia chiamando 'ToBase64String' su un buffer cancellato a meno che non sia il pezzo finale. – Blorgbeard

+3

Il problema potrebbe essere nel codice che converte i file da base64 in file binario. Leggi caratteri in blocchi di quattro o Mulitple of Four. – Vova

+0

@Blorgbeard - Sto usando il secondaryBuffer per contenere il contenuto della prima lettura/corrente dal file. Poi ho letto di nuovo, cercando un ritorno di "0" per indicare che sto elaborando il blocco finale. Il blocco finale viene ridimensionato in modo che sia sufficientemente grande da contenere i dati che vengono codificati. Per esempio. - se il buffer è stato impostato su 600.000, ma l'ultima lettura è lunga 1000 byte, non è necessario passare un byte [] contenente 600.000 elementi. Se non sono sul blocco finale, allora elaborerò 'secondaryBuffer', che contiene i dati richiesti. – CaptainCobol

risposta

10

Sulla base del codice mostrato nel the blog dal Wiktor Zychla il seguente codice funziona. Questa stessa soluzione è indicata nella sezione commenti del Convert.ToBase64String come ha sottolineato 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 
} 

Il codice utilizza le classi standard che si trovano in System.Security.Cryptography namespace e utilizza un CryptoStream e la FromBase64Transform e la sua controparte ToBase64Transform

+3

Questa è davvero la risposta giusta! La documentazione MSDN per il metodo 'Convert.ToBase64String' (https://msdn.microsoft.com/en-us/library/s70ad5f6(v=vs.100).aspx) contiene ** Avviso ** Importante nelle ** Note ** sezione raccomandando proprio questo. –

+0

@rene - Non avevo familiarità con queste trasformazioni. Questo sembra quello che stavo cercando sin dall'inizio. Farò sicuramente un tentativo. – CaptainCobol

8

È possibile evitare di utilizzare un buffer secondario passando offset e lunghezza Convert.ToBase64String, in questo modo:

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

Quanto sopra dovrebbe funzionare, ma penso Rene's answer è in realtà la soluzione migliore.

+0

Il comando 'stream.Read' cancella il buffer di input prima della lettura? Se stai richiedendo 3 e legge 2, l'ultimo byte contiene un vecchio valore? –

+3

@DaveZych: Non lo cancella, ma non importa, dato che passi 'offset' e' length' al metodo 'Convert.ToBase64String' –

+0

@DaveZych, sei corretto, ed è per questo che cancello il buffer prima di ogni nuova lettura. – CaptainCobol

1

di questo codice:

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 non è sicuro in questo scenario. Lo stavo usando originariamente, ma ho scoperto che i miei array di copia erano riempiti per metà di null. Vedi: http://stackoverflow.com/a/1390023/4659717 – CaptainCobol

+0

Perché non è sicuro? Sono sicuro che lo sia. Ad ogni modo, ho notato che la risposta di Blorgbeard è in realtà migliore, fa lo stesso che faccio io, a parte il fatto che non usa Buffer.BlockCopy, utilizza un altro overload del metodo ToBase64String. –

+0

Se segui il link che ho fornito, MusiGenesis lo spiega bene. I miei array erano archiviati per metà, con contenuti e mezzo pieno di null. I parametri Buffer.BlockCopy sono basati su byte, anziché su indici. – CaptainCobol

Problemi correlati