2017-03-02 5 views
14

Tenir compte:Comment convertir l'encodage d'un fichier volumineux (> 1 Go) en taille - vers Windows 1252 sans exception de mémoire insuffisante?

public static void ConvertFileToUnicode1252(string filePath, Encoding srcEncoding) 
{ 
    try 
    { 
     StreamReader fileStream = new StreamReader(filePath); 
     Encoding targetEncoding = Encoding.GetEncoding(1252); 

     string fileContent = fileStream.ReadToEnd(); 
     fileStream.Close(); 

     // Saving file as ANSI 1252 
     Byte[] srcBytes = srcEncoding.GetBytes(fileContent); 
     Byte[] ansiBytes = Encoding.Convert(srcEncoding, targetEncoding, srcBytes); 
     string ansiContent = targetEncoding.GetString(ansiBytes); 

     // Now writes contents to file again 
     StreamWriter ansiWriter = new StreamWriter(filePath, false); 
     ansiWriter.Write(ansiContent); 
     ansiWriter.Close(); 
     //TODO -- log success details 
    } 
    catch (Exception e) 
    { 
     throw e; 
     // TODO -- log failure details 
    } 
} 

La pièce ci-dessus code renvoie une exception hors de la mémoire pour les gros fichiers et ne fonctionne que pour les fichiers de petite taille.

+12

Pouvez-vous pas le faire ligne par ligne? – BugFinder

+8

Vous n'avez pas besoin de lire tout le contenu avec ReadToEnd. Lire morceau, convertir, écrire, répéter. – Evk

+3

Utilisez 'foreach (chaîne de caractères dans File.ReadLines (filePath)) ... ligne de processus ...' –

Répondre

12

Je pense toujours en utilisant un StreamReader et un StreamWriter mais des blocs de lecture de caractères au lieu de tout à la fois ou ligne par ligne est la solution la plus élégante. Il ne suppose pas arbitrairement que le fichier se compose de lignes de longueur gérable et qu'il ne rompt pas avec les codages de caractères multi-octets.

public static void ConvertFileEncoding(string srcFile, Encoding srcEncoding, string destFile, Encoding destEncoding) 
{ 
    using (var reader = new StreamReader(srcFile, srcEncoding)) 
    using (var writer = new StreamWriter(destFile, false, destEncoding)) 
    { 
     char[] buf = new char[4096]; 
     while (true) 
     { 
      int count = reader.Read(buf, 0, buf.Length); 
      if (count == 0) 
       break; 

      writer.Write(buf, 0, count); 
     } 
    } 
} 

(je veux StreamReader avait une méthode CopyTo comme Stream ne, si elle avait, ce serait essentiellement une seule ligne!)

+0

Merci @ Matti. Cette question m'aide à accomplir la tâche. Je pourrais convertir l'encodage du dossier plus de 1.5GB sans aucune exception. –

1

Ne pas readToEnd et le lire comme ligne par ligne ou X caractères à la fois. Si vous lisez à la fin, vous mettez tout votre fichier dans le tampon à la fois.

-1

Essayez ceci:

using (FileStream fileStream = new FileStream(filePath, FileMode.Open)) 
{ 
    int size = 4096; 
    Encoding targetEncoding = Encoding.GetEncoding(1252); 
    byte[] byteData = new byte[size]; 

    using (FileStream outputStream = new FileStream(outputFilepath, FileMode.Create)) 
    { 
     int byteCounter = 0; 

     do 
     { 
      byteCounter = fileStream.Read(byteData, 0, size); 

      // Convert the 4k buffer 
      byteData = Encoding.Convert(srcEncoding, targetEncoding, byteData); 

      if (byteCounter > 0) 
      { 
       outputStream.Write(byteData, 0, byteCounter); 
      } 
     } 
     while (byteCounter > 0); 

     inputStream.Close(); 
    } 
} 

aurait peut-être des erreurs de syntaxe comme je l'ai fait de la mémoire, mais voilà comment je travaille avec de gros fichiers, lire dans un morceau à la fois, faire un peu de traitement et enregistrer le morceau en arrière. C'est vraiment la seule façon de le faire (streaming) sans compter sur un overhead massif d'IO pour tout lire et une énorme consommation de RAM pour tout stocker, tout convertir en mémoire et ensuite tout sauvegarder.

Vous pouvez toujours ajuster la taille de la mémoire tampon.

Si vous voulez que votre ancienne méthode de travailler sans jeter le OutOfMemoryException, vous devez dire au Garbage Collector pour permettre des objets très volumineux.

En App.config, sous <runtime> ajouter cette ligne suivante (vous ne devriez pas avoir besoin avec mon code, mais il est bon de savoir):

<gcAllowVeryLargeObjects enabled="true" /> 
+4

Cela ne fonctionne tout simplement pas avec toutes les entrées.L'entrée est en UTF8, et il n'y a aucune garantie qu'en lisant exactement 4K octets, vous ne liriez pas dans un caractère partiel qui a été encodé dans plus d'un octet. Si cela se produit, il ne sera pas lu correctement et vous aurez des données invalides. –

+0

Je ne vois nulle part dans la question se référant à UTF8, l'encodage source n'est-il pas passé en paramètre? Oui, il va falloir peaufiner pour UTF8 mais si votre fichier est sur une seule ligne (pour économiser de l'espace en n'utilisant pas d'espaces inutiles ou de nouvelles lignes, par exemple XML), faire ligne par ligne ne fonctionnera pas de streaming le fichier. La taille du tampon peut toujours être ajustée à chaque itération en fonction des données partielles lues. –

+0

Le constructeur ['StreamReader (chaîne de caractères)'] (https://msdn.microsoft.com/fr-fr/library/f2ke0fzy (v = vs.110) .aspx) constructeur que l'OP utilise ouvre le flux d'entrée en tant que UTF8. Voir la documentation liée. Dans l'événement extrêmement improbable que tout le texte est sur une ligne, alors l'approche correcte est d'utiliser le ['StreamReader.Read()'] (https://msdn.microsoft.com/en-us/library/9kstw824 (v = vs.110) .aspx) surcharge qui lit un nombre spécifié de caractères d'un fichier. NE JAMAIS lire un tampon de taille fixe à lire à partir d'un fichier dont les caractères peuvent avoir un codage de longueur variable. C'est presque toujours un bug. –