web-dev-qa-db-fra.com

Comment écrire efficacement un gros fichier texte en C #?

Je crée une méthode en C # qui génère un fichier texte pour un Google Product Feed . Le flux contiendra plus de 30 000 enregistrements et le fichier texte pèse actuellement à ~ 7 Mo.

Voici le code que j'utilise actuellement (certaines lignes ont été supprimées par souci de concision).

public static void GenerateTextFile(string filePath) {

  var sb = new StringBuilder(1000);
  sb.Append("availability").Append("\t");
  sb.Append("condition").Append("\t");
  sb.Append("description").Append("\t");
  // repetitive code hidden for brevity ...
  sb.Append(Environment.NewLine);

  var items = inventoryRepo.GetItemsForSale();

  foreach (var p in items) {
    sb.Append("in stock").Append("\t");
    sb.Append("used").Append("\t");
    sb.Append(p.Description).Append("\t");
    // repetitive code hidden for brevity ...
    sb.AppendLine();
  }

  using (StreamWriter outfile = new StreamWriter(filePath)) {
      result.Append("Writing text file to disk.").AppendLine();
      outfile.Write(sb.ToString());
  }
}

Je me demande si StringBuilder est le bon outil pour le travail. Y aurait-il des gains de performances si j'utilisais plutôt un TextWriter?

Je ne connais pas beaucoup les performances de IO donc toute aide ou amélioration générale serait appréciée. Merci.

37
jessegavin

Les opérations d'E/S sur les fichiers sont généralement bien optimisées dans les systèmes d'exploitation modernes. Vous ne devriez pas essayer d'assembler la chaîne entière du fichier en mémoire ... écrivez-la simplement morceau par morceau. FileStream se chargera de la mise en mémoire tampon et d'autres considérations de performances.

Vous pouvez effectuer ce changement facilement en déplaçant:

using (StreamWriter outfile = new StreamWriter(filePath)) {

en haut de la fonction, et se débarrasser de l'écriture StringBuilder directement dans le fichier.

Il y a plusieurs raisons pour lesquelles vous devriez éviter de créer de grandes chaînes en mémoire:

  1. Il peut en fait fonctionner moins bien, car le StringBuilder doit augmenter sa capacité lorsque vous y écrivez, ce qui entraîne une réallocation et une copie de la mémoire.
  2. Il peut nécessiter plus de mémoire que vous ne pouvez en affecter physiquement - ce qui peut entraîner l'utilisation de la mémoire virtuelle (le fichier d'échange), qui est beaucoup plus lente que la RAM.
  3. Pour les fichiers vraiment volumineux (> 2 Go), vous manquerez d'espace d'adressage (sur les plates-formes 32 bits) et ne réussirez jamais.
  4. Pour écrire le contenu de StringBuilder dans un fichier, vous devez utiliser ToString() qui double efficacement la consommation de mémoire du processus car les deux copies doivent être en mémoire pendant un certain temps. Cette opération peut également échouer si votre espace d'adressage est suffisamment fragmenté, de sorte qu'un seul bloc de mémoire contigu ne peut pas être alloué.
70
LBushkin

Déplacez simplement l'instruction using pour qu'elle englobe l'ensemble de votre code et écrivez directement dans le fichier. Je ne vois aucun intérêt à tout garder en mémoire en premier.

26
Jon Skeet

Écrivez une chaîne à la fois à l'aide de StreamWriter.Write plutôt que de tout mettre en cache dans un StringBuilder.

12
Alex Humphrey