Quel est le moyen le plus rapide de lire un fichier texte ligne par ligne?

319

Je veux lire un fichier texte ligne par ligne. Je voulais savoir si je le fais le plus efficacement possible dans le cadre des choses .NET C #.

Voici ce que j'essaie jusqu'à présent:

var filestream = new System.IO.FileStream(textFilePath,
                                          System.IO.FileMode.Open,
                                          System.IO.FileAccess.Read,
                                          System.IO.FileShare.ReadWrite);
var file = new System.IO.StreamReader(filestream, System.Text.Encoding.UTF8, true, 128);

while ((lineOfText = file.ReadLine()) != null)
{
    //Do something with the lineOfText
}
Loren C Fortner
la source
7
En Fastestvous dire des perspectives de performance ou de développement?
sll
1
Cela va verrouiller le fichier pendant la durée de la méthode. Vous pouvez utiliser File.ReadAllLines dans un tableau puis traiter le tableau.
Kell
17
BTW, joindre filestream = new FileStreamdans une using()déclaration pour éviter d'éventuels problèmes ennuyeux avec le
descripteur de
En ce qui concerne la fermeture de FileStream utilise l'instruction (), voir StackOverflow concernant la méthode recommandée: StackOverflow utilisant l'instruction filestream streamreader
deegee
Je pense que ReadToEnd () est plus rapide.
Dan Gifford

Réponses:

315

Pour trouver le moyen le plus rapide de lire un fichier ligne par ligne, vous devrez effectuer une analyse comparative. J'ai fait quelques petits tests sur mon ordinateur mais vous ne pouvez pas vous attendre à ce que mes résultats s'appliquent à votre environnement.

Utilisation de StreamReader.ReadLine

C'est fondamentalement votre méthode. Pour une raison quelconque, vous définissez la taille du tampon sur la plus petite valeur possible (128). Augmenter cela augmentera généralement les performances. La taille par défaut est 1 024 et d'autres bons choix sont 512 (la taille du secteur dans Windows) ou 4 096 (la taille du cluster dans NTFS). Vous devrez exécuter un benchmark pour déterminer une taille de tampon optimale. Un tampon plus gros n'est - sinon plus rapide - du moins pas plus lent qu'un tampon plus petit.

const Int32 BufferSize = 128;
using (var fileStream = File.OpenRead(fileName))
  using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) {
    String line;
    while ((line = streamReader.ReadLine()) != null)
      // Process line
  }

Le FileStreamconstructeur vous permet de spécifier FileOptions . Par exemple, si vous lisez un gros fichier séquentiellement du début à la fin, vous pouvez en bénéficier FileOptions.SequentialScan. Encore une fois, l'analyse comparative est la meilleure chose que vous puissiez faire.

Utilisation de File.ReadLines

Cela ressemble beaucoup à votre propre solution, sauf qu'elle est implémentée à l'aide d'un StreamReaderavec une taille de tampon fixe de 1 024. Sur mon ordinateur, cela entraîne des performances légèrement meilleures par rapport à votre code avec une taille de tampon de 128. Cependant, vous pouvez obtenir la même augmentation de performances en utilisant une taille de tampon plus grande. Cette méthode est implémentée à l'aide d'un bloc itérateur et ne consomme pas de mémoire pour toutes les lignes.

var lines = File.ReadLines(fileName);
foreach (var line in lines)
  // Process line

Utilisation de File.ReadAllLines

Cela ressemble beaucoup à la méthode précédente, sauf que cette méthode développe une liste de chaînes utilisées pour créer le tableau de lignes renvoyé, de sorte que les besoins en mémoire sont plus élevés. Cependant, il revient String[]et ne IEnumerable<String>vous permet pas d'accéder au hasard aux lignes.

var lines = File.ReadAllLines(fileName);
for (var i = 0; i < lines.Length; i += 1) {
  var line = lines[i];
  // Process line
}

Utilisation de String.Split

Cette méthode est considérablement plus lente, au moins sur les gros fichiers (testée sur un fichier de 511 Ko), probablement en raison de la façon dont elle String.Splitest implémentée. Il alloue également un tableau pour toutes les lignes, augmentant la mémoire requise par rapport à votre solution.

using (var streamReader = File.OpenText(fileName)) {
  var lines = streamReader.ReadToEnd().Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
  foreach (var line in lines)
    // Process line
}

Ma suggestion est d'utiliser File.ReadLinescar il est propre et efficace. Si vous avez besoin d'options de partage spéciales (par exemple que vous utilisez FileShare.ReadWrite), vous pouvez utiliser votre propre code mais vous devez augmenter la taille du tampon.

Martin Liversage
la source
1
Merci pour cela - votre inclusion du paramètre de taille de tampon sur le constructeur de StreamReader a été vraiment utile. Je diffuse depuis l'API S3 d'Amazon, et l'utilisation d'une taille de tampon correspondante accélère considérablement les choses en conjonction avec ReadLine ().
Richard K.13
Je ne comprends pas. En théorie, la grande majorité du temps passé à lire le fichier serait le temps de recherche sur le disque et les frais généraux de traitement des flux, comme ce que vous feriez avec File.ReadLines. File.ReadLines, d'autre part, est censé lire tout d'un fichier dans la mémoire en une seule fois. Comment pourrait-il être pire dans les performances?
2015 à 15h55
2
Je ne peux pas dire sur les performances de vitesse mais une chose est sûre: c'est bien pire sur la consommation mémoire. Si vous devez gérer de très gros fichiers (Go par exemple), cela est très critique. Encore plus si cela signifie qu'il doit échanger de la mémoire. Côté vitesse, vous pouvez ajouter que ReadAllLine doit lire TOUTES les lignes AVANT de renvoyer le résultat retardant le traitement. Dans certains scénarios, l'IMPRESSION de la vitesse est plus importante que la vitesse brute.
bkqc
Si vous lisez le flux sous forme de tableaux d'octets, il lira le fichier de 20% à 80% plus rapidement (d'après les tests que j'ai effectués). Ce dont vous avez besoin est d'obtenir le tableau d'octets et de le convertir en chaîne. C'est comme ça que je l'ai fait: pour lire, utilisez stream.Read () Vous pouvez faire une boucle pour la lire en morceaux. Après avoir ajouté tout le contenu dans un tableau d'octets (utilisez System.Buffer.BlockCopy ), vous devrez convertir les octets en chaîne: Encoding.Default.GetString (byteContent, 0, byteContent.Length - 1) .Split (nouvelle chaîne [ ] {"\ r \ n", "\ r", "\ n"}, StringSplitOptions.None);
Kim Lage
200

Si vous utilisez .NET 4, utilisez simplement File.ReadLinesqui fait tout pour vous. Je soupçonne qu'il est à peu près le même que le vôtre, sauf qu'il peut également utiliser FileOptions.SequentialScanet un tampon plus grand (128 semble très petit).

Jon Skeet
la source
Un autre avantage ReadLines()est qu'il est paresseux et fonctionne donc bien avec LINQ.
stt106
35

Bien que ce File.ReadAllLines()soit l'un des moyens les plus simples de lire un fichier, c'est aussi l'un des plus lents.

Si vous souhaitez simplement lire des lignes dans un fichier sans faire grand-chose, selon ces repères , le moyen le plus rapide de lire un fichier est la méthode séculaire de:

using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
               //do minimal amount of work here
        }
}

Cependant, si vous devez faire beaucoup avec chaque ligne, cet article conclut que la meilleure façon est la suivante (et il est plus rapide de pré-allouer une chaîne [] si vous savez combien de lignes vous allez lire):

AllLines = new string[MAX]; //only allocate memory here

using (StreamReader sr = File.OpenText(fileName))
{
        int x = 0;
        while (!sr.EndOfStream)
        {
               AllLines[x] = sr.ReadLine();
               x += 1;
        }
} //Finished. Close the file

//Now parallel process each line in the file
Parallel.For(0, AllLines.Length, x =>
{
    DoYourStuff(AllLines[x]); //do your work here
});
Free Coder 24
la source
13

Utilisez le code suivant:

foreach (string line in File.ReadAllLines(fileName))

C'était une énorme différence dans les performances de lecture.

Cela se fait au détriment de la consommation de mémoire, mais ça vaut vraiment le coup!

user2671536
la source
je préfère File.ReadLines (cliquez sur moi) queFile.ReadAllLines
newbieguy
5

Il y a un bon sujet à ce sujet dans la question de débordement de pile . .

Ça dit:

ReadAllLines charge toutes les lignes en mémoire et renvoie une chaîne []. Très bien si le fichier est petit. Si le fichier est plus volumineux que ce qui peut tenir en mémoire, vous manquerez de mémoire.

ReadLines, d'autre part, utilise return return pour renvoyer une ligne à la fois. Avec lui, vous pouvez lire n'importe quel fichier de taille. Il ne charge pas le fichier entier en mémoire.

Supposons que vous vouliez trouver la première ligne contenant le mot "foo", puis quittez. En utilisant ReadAllLines, vous devez lire l'intégralité du fichier en mémoire, même si "foo" apparaît sur la première ligne. Avec ReadLines, vous ne lisez qu'une seule ligne. Lequel serait le plus rapide?

Marcel James
la source
4

Si la taille du fichier n'est pas grande, il est plus rapide de lire le fichier entier et de le diviser ensuite

var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                              StringSplitOptions.RemoveEmptyEntries);
Saeed Amiri
la source
6
File.ReadAllLines()
jgauffin
@jgauffin Je ne sais pas derrière l'implémentation de file.ReadAlllines () mais je pense qu'il a un tampon limité et le tampon fileReadtoEnd devrait être plus grand, donc le nombre d'accès au fichier sera diminué de cette façon, et faire string.Split dans le la taille du fichier n'est pas grande est plus rapide que l'accès multiple au fichier.
Saeed Amiri
Je doute que la File.ReadAllLinestaille du tampon soit fixe, car la taille du fichier est connue.
jgauffin
1
@jgauffin: dans .NET 4.0 File.ReadAllLinescrée une liste et ajoute à cette liste dans une boucle en utilisant StreamReader.ReadLine(avec une réallocation potentielle du tableau sous-jacent). Cette méthode utilise une taille de tampon par défaut de 1024. Cela StreamReader.ReadToEndévite la partie d'analyse de ligne et la taille du tampon peut être définie dans le constructeur si vous le souhaitez.
Martin Liversage
Il serait utile de définir "BIG" en ce qui concerne la taille du fichier.
Paul
2

Si vous avez suffisamment de mémoire, j'ai trouvé des gains de performances en lisant le fichier entier dans un flux de mémoire , puis en ouvrant un lecteur de flux sur celui-ci pour lire les lignes. Tant que vous prévoyez de lire le fichier dans son intégralité, cela peut apporter quelques améliorations.

Kibbee
la source
1
File.ReadAllLinessemble être un meilleur choix alors.
jgauffin
2

Vous ne pouvez pas aller plus vite si vous souhaitez utiliser une API existante pour lire les lignes. Mais lire des morceaux plus gros et trouver manuellement chaque nouvelle ligne dans le tampon de lecture serait probablement plus rapide.

jgauffin
la source