Qual è il modo più veloce per leggere un file di testo riga per riga?


319

Voglio leggere un file di testo riga per riga. Volevo sapere se lo sto facendo nel modo più efficiente possibile nell'ambito di .NET C #.

Questo è quello che sto provando finora:

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
}

7
Con Fastestvuoi dire dal punto di vista delle prestazioni o di sviluppo?
sabato

1
Questo bloccherà il file per la durata del metodo. È possibile utilizzare File.ReadAllLines in un array, quindi elaborare l'array.
Kell,

17
A proposito, allegare filestream = new FileStreamnella using()dichiarazione per evitare possibili fastidiosi problemi con la gestione dei file bloccati
sll

Per quanto riguarda la chiusura di FileStream sta utilizzando l'istruzione (), vedere StackOverflow per quanto riguarda il metodo consigliato: StackOverflow utilizzando l'istruzione stream stream filderream
deegee

Penso che ReadToEnd () sia più veloce.
Dan Gifford,

Risposte:


315

Per trovare il modo più veloce per leggere un file riga per riga dovrai fare alcuni benchmark. Ho eseguito alcuni piccoli test sul mio computer ma non puoi aspettarti che i miei risultati si applichino al tuo ambiente.

Utilizzando StreamReader.ReadLine

Questo è fondamentalmente il tuo metodo. Per qualche motivo si imposta la dimensione del buffer sul valore più piccolo possibile (128). Aumentare questo in generale aumenterà le prestazioni. La dimensione predefinita è 1.024 e altre buone scelte sono 512 (la dimensione del settore in Windows) o 4.096 (la dimensione del cluster in NTFS). Dovrai eseguire un benchmark per determinare una dimensione ottimale del buffer. Un buffer più grande è - se non più veloce - almeno non più lento di un buffer più piccolo.

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
  }

Il FileStreamcostruttore consente di specificare FileOptions . Ad esempio, se stai leggendo un file di grandi dimensioni in sequenza dall'inizio alla fine, potresti trarne vantaggio FileOptions.SequentialScan. Ancora una volta, il benchmarking è la cosa migliore che puoi fare.

Utilizzando File.ReadLines

Questo è molto simile alla propria soluzione, tranne per il fatto che è implementato usando un StreamReadercon una dimensione del buffer fissa di 1.024. Sul mio computer ciò si traduce in prestazioni leggermente migliori rispetto al codice con una dimensione del buffer di 128. Tuttavia, è possibile ottenere lo stesso aumento delle prestazioni utilizzando una dimensione del buffer maggiore. Questo metodo è implementato usando un blocco iteratore e non consuma memoria per tutte le linee.

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

Utilizzando File.ReadAllLines

Questo è molto simile al metodo precedente, tranne per il fatto che questo metodo aumenta un elenco di stringhe utilizzate per creare l'array di righe restituito, quindi i requisiti di memoria sono più elevati. Tuttavia, ritorna String[]e non IEnumerable<String>ti consente di accedere in modo casuale alle linee.

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

Utilizzando String.Split

Questo metodo è notevolmente più lento, almeno su file di grandi dimensioni (testato su un file da 511 KB), probabilmente a causa della sua String.Splitimplementazione. Alloca anche un array per tutte le linee aumentando la memoria richiesta rispetto alla tua soluzione.

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

Il mio consiglio è di usarlo File.ReadLinesperché è pulito ed efficiente. Se sono necessarie opzioni di condivisione speciali (ad esempio, si utilizza FileShare.ReadWrite), è possibile utilizzare il proprio codice ma è necessario aumentare le dimensioni del buffer.


1
Grazie per questo - l'inclusione del parametro di dimensione del buffer nel costruttore di StreamReader è stata davvero utile. Sto trasmettendo in streaming dall'API S3 di Amazon e l'utilizzo di una dimensione del buffer corrispondente accelera notevolmente le cose insieme a ReadLine ().
Richard K.

Non capisco. In teoria, la stragrande maggioranza del tempo trascorso a leggere il file sarebbe il tempo di ricerca su disco e le spese generali di gestione dei flussi, come quello che faresti con File.ReadLines. File.ReadLines, d'altra parte, dovrebbe leggere tutto di un file in memoria in una volta sola. Come potrebbe essere peggio in termini di prestazioni?
h9uest

2
Non posso dire delle prestazioni in termini di velocità, ma una cosa è certa: è molto peggio per il consumo di memoria. Se devi gestire file di dimensioni molto grandi (ad esempio GB), questo è molto importante. Ancora di più se ciò significa che deve scambiare memoria. Per quanto riguarda la velocità, è possibile aggiungere che ReadAllLine deve leggere TUTTE le righe PRIMA di restituire il risultato ritardando l'elaborazione. In alcuni scenari, l'IMPRESSIONE della velocità è più importante della velocità pura.
bkqc,

Se leggi lo stream come array di byte, leggerà il file dal 20% all'80% più veloce (dai test che ho fatto). Ciò di cui hai bisogno è ottenere l'array di byte e convertirlo in stringa. È così che l'ho fatto: per leggere usa stream.Read () Puoi fare un ciclo per farlo leggere in blocchi. Dopo aver aggiunto l'intero contenuto in un array di byte (utilizzare System.Buffer.BlockCopy ) dovrai convertire i byte in stringa: Encoding.Default.GetString (byteContent, 0, byteContent.Length - 1) .Split (nuova stringa [ ] {"\ r \ n", "\ r", "\ n"}, StringSplitOptions.None);
Kim Lage,

200

Se stai usando .NET 4, usa semplicemente File.ReadLinesciò che fa tutto per te. Ho il sospetto che sia più o meno lo stesso del tuo, tranne che può anche usare FileOptions.SequentialScanun buffer più grande (128 sembra molto piccolo).


Un altro vantaggio ReadLines()è che è pigro, quindi funziona bene con LINQ.
stt106,

35

Mentre File.ReadAllLines()è uno dei modi più semplici per leggere un file, è anche uno dei più lenti.

Se vuoi solo leggere le righe in un file senza fare molto, secondo questi benchmark , il modo più veloce per leggere un file è il vecchio metodo di:

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

Tuttavia, se devi fare molto con ogni riga, questo articolo conclude che il modo migliore è il seguente (ed è più veloce pre-allocare una stringa [] se sai quante righe stai per leggere):

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

13

Usa il seguente codice:

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

Questa era una GRANDE differenza nelle prestazioni di lettura.

Viene a scapito del consumo di memoria, ma ne vale assolutamente la pena!


preferirei File.ReadLines (fai clic su di me) diFile.ReadAllLines
newbieguy

5

C'è un buon argomento al riguardo nella domanda di Stack Overflow Il "rendimento ritorno" è più lento del ritorno alla "vecchia scuola"? .

Dice:

ReadAllLines carica tutte le righe in memoria e restituisce una stringa []. Va bene se il file è piccolo. Se il file è più grande di quello che si adatta alla memoria, la memoria si esaurirà.

ReadLines, d'altra parte, utilizza il rendimento return per restituire una riga alla volta. Con esso, puoi leggere file di qualsiasi dimensione. Non carica l'intero file in memoria.

Supponi di voler trovare la prima riga che contiene la parola "pippo" e quindi esci. Usando ReadAllLines, dovresti leggere l'intero file in memoria, anche se "pippo" si verifica sulla prima riga. Con ReadLines, leggi solo una riga. Quale sarebbe più veloce?


4

Se la dimensione del file non è grande, è più veloce leggere l'intero file e dividerlo in seguito

var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                              StringSplitOptions.RemoveEmptyEntries);

6
File.ReadAllLines()
Jgauffin,

@jgauffin Non so dietro l'implementazione di file.ReadAlllines () ma penso che abbia un buffer limitato e il buffer fileReadtoEnd dovrebbe essere maggiore, quindi il numero di accesso al file verrà diminuito in questo modo e facendo string. la dimensione del file case non è grande è più veloce dell'accesso multiplo al file.
Saeed Amiri,

Dubito che File.ReadAllLinesabbia una dimensione del buffer fissa poiché la dimensione del file è nota.
Jgauffin,

1
@jgauffin: in .NET 4.0 File.ReadAllLinescrea un elenco e lo aggiunge in un ciclo usando StreamReader.ReadLine(con potenziale riallocazione dell'array sottostante). Questo metodo utilizza una dimensione del buffer predefinita di 1024. StreamReader.ReadToEndEvita la parte di analisi della linea e la dimensione del buffer può essere impostata nel costruttore, se lo si desidera.
Martin Liversage,

Sarebbe utile definire "GRANDE" per quanto riguarda le dimensioni del file.
Paul,

2

Se hai abbastanza memoria, ho trovato alcuni miglioramenti delle prestazioni leggendo l'intero file in un flusso di memoria e quindi aprendo un lettore di flussi su quello per leggere le righe. Fintanto che in realtà pianifichi di leggere l'intero file comunque, questo può produrre alcuni miglioramenti.


1
File.ReadAllLinessembra essere una scelta migliore allora.
Jgauffin,

2

Non puoi andare più veloce se vuoi usare un'API esistente per leggere le righe. Ma leggere blocchi più grandi e trovare manualmente ogni nuova riga nel buffer di lettura sarebbe probabilmente più veloce.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.