StreamReader.Readline () è davvero il metodo più veloce per contare le righe in un file?

Guardando in giro per un po ‘ho trovato alcune discussioni su come capire il numero di linee in un file.

Ad esempio questi tre:
c # come faccio a contare le linee in un file di testo
Determina il numero di linee all’interno di un file di testo
Come contare le linee velocemente?

Quindi, sono andato avanti e ho finito per usare quello che sembra essere il metodo più efficiente (almeno per quanto riguarda la memoria?) Che ho potuto trovare:

private static int countFileLines(string filePath) { using (StreamReader r = new StreamReader(filePath)) { int i = 0; while (r.ReadLine() != null) { i++; } return i; } } 

Ma questo richiede per sempre quando le linee stesse dal file sono molto lunghe. Non c’è davvero una soluzione più veloce a questo?

Ho provato a utilizzare StreamReader.Read() o StreamReader.Peek() ma non riesco (o non so come) a far sì che entrambi passino alla riga successiva non appena ci sono “cose” (chars? text?).

Qualche idea per favore?


CONCLUSIONE / RISULTATI (Dopo aver eseguito alcuni test in base alle risposte fornite):

Ho testato i 5 metodi seguenti su due file diversi e ho ottenuto risultati coerenti che sembrano indicare che il vecchio StreamReader.ReadLine() è ancora uno dei modi più veloci … Per essere onesti, sono perplesso dopo tutti i commenti e discussione nelle risposte.

File 1:
Dimensione: 3,631 KB
Righe: 56,870

Risultati in secondi per il file n. 1:
0,02 -> metodo ReadLine.
0.04 -> Metodo di lettura.
0,29 -> metodo ReadByte.
0,25 -> Readlines.Count metodo.
0.04 -> Metodo ReadWithBufferSize.

File # 2:
Dimensione: 14,499 KB
Righe: 213.424

Risultati in secondi per il file n. 1:
0.08 -> metodo ReadLine.
0.19 -> Metodo di lettura.
1.15 -> Metodo ReadByte.
1.02 -> Readlines.Count metodo.
0.08 -> Metodo ReadWithBufferSize.

Ecco i 5 metodi che ho testato in base a tutti i feedback che ho ricevuto:

 private static int countWithReadLine(string filePath) { using (StreamReader r = new StreamReader(filePath)) { int i = 0; while (r.ReadLine() != null) { i++; } return i; } } private static int countWithRead(string filePath) { using (StreamReader _reader = new StreamReader(filePath)) { int c = 0, count = 0; while ((c = _reader.Read()) != -1) { if (c == 10) { count++; } } return count; } } private static int countWithReadByte(string filePath) { using (Stream s = new FileStream(filePath, FileMode.Open)) { int i = 0; int b; b = s.ReadByte(); while (b >= 0) { if (b == 10) { i++; } b = s.ReadByte(); } return i; } } private static int countWithReadLinesCount(string filePath) { return File.ReadLines(filePath).Count(); } private static int countWithReadAndBufferSize(string filePath) { int bufferSize = 512; using (Stream s = new FileStream(filePath, FileMode.Open)) { int i = 0; byte[] b = new byte[bufferSize]; int n = 0; n = s.Read(b, 0, bufferSize); while (n > 0) { i += countByteLines(b, n); n = s.Read(b, 0, bufferSize); } return i; } } private static int countByteLines(byte[] b, int n) { int i = 0; for (int j = 0; j < n; j++) { if (b[j] == 10) { i++; } } return i; } 

No non lo è. Il punto è – si materializza le stringhe, che non è necessario.

Per CONTO è meglio ignorare la parte “stringa” e passare alla parte “linea”.

una LINE è una serie di byte che terminano con \ r \ n (13, 10 – CR LF) o un altro marker.

Corri lungo i byte, in un stream bufferizzato, contando il numero di aspetti del tuo marcatore di fine riga.

Il modo migliore per sapere come fare in fretta è pensare al modo più veloce per farlo senza usare C / C ++.

Nell’assemblaggio è presente un’operazione a livello di CPU che analizza la memoria per un carattere, quindi in assembly si dovrebbe fare quanto segue

  • Leggi la grande parte (o tutti) del file in memoria
  • Esegui il comando SCASB
  • Ripeti se necessario

Quindi, in C #, vuoi che il compilatore si avvicini il più ansible a questo.

Ho provato diversi metodi e testato le loro prestazioni:

Quello che legge un singolo byte è circa il 50% più lento rispetto agli altri metodi. Gli altri metodi ritornano sempre nello stesso intervallo di tempo. Puoi provare a creare thread e farlo in modo asincrono, così mentre stai aspettando una lettura puoi iniziare a elaborare una lettura precedente. Mi sembra un mal di testa.

Vorrei andare con l’unico rivestimento: File.ReadLines(filePath).Count(); esegue così come gli altri metodi che ho provato.

  private static int countFileLines(string filePath) { using (StreamReader r = new StreamReader(filePath)) { int i = 0; while (r.ReadLine() != null) { i++; } return i; } } private static int countFileLines2(string filePath) { using (Stream s = new FileStream(filePath, FileMode.Open)) { int i = 0; int b; b = s.ReadByte(); while (b >= 0) { if (b == 10) { i++; } b = s.ReadByte(); } return i + 1; } } private static int countFileLines3(string filePath) { using (Stream s = new FileStream(filePath, FileMode.Open)) { int i = 0; byte[] b = new byte[bufferSize]; int n = 0; n = s.Read(b, 0, bufferSize); while (n > 0) { i += countByteLines(b, n); n = s.Read(b, 0, bufferSize); } return i + 1; } } private static int countByteLines(byte[] b, int n) { int i = 0; for (int j = 0; j < n; j++) { if (b[j] == 10) { i++; } } return i; } private static int countFileLines4(string filePath) { return File.ReadLines(filePath).Count(); } 
 public static int CountLines(Stream stm) { StreamReader _reader = new StreamReader(stm); int c = 0, count = 0; while ((c = _reader.Read()) != -1) { if (c == '\n') { count++; } } return count; } 

Sì, leggere linee del genere è il modo più rapido e semplice in qualsiasi senso pratico.

Non ci sono scorciatoie qui. I file non sono basati sulla linea, quindi devi leggere ogni singolo byte dal file per determinare quante linee ci sono.

Come ha sottolineato TomTom, la creazione delle stringhe non è strettamente necessaria per contare le linee, ma la stragrande maggioranza del tempo speso attenderà che i dati vengano letti dal disco. Scrivere un algoritmo molto più complicato potrebbe forse ridurre una percentuale del tempo di esecuzione, e aumenterebbe notevolmente il tempo per scrivere e testare il codice.

Esistono numerosi modi per leggere un file. Di solito, il modo più veloce è il più semplice:

 using (StreamReader sr = File.OpenText(fileName)) { string s = String.Empty; while ((s = sr.ReadLine()) != null) { //do what you gotta do here } } 

Questa pagina offre un ottimo confronto delle prestazioni tra diverse tecniche tra cui l’uso di BufferedReader, la lettura in oggetti StringBuilder e in un intero array.

StreamReader non è il modo più veloce per leggere i file in generale a causa del piccolo overhead dalla codifica dei byte ai caratteri, quindi la lettura del file in un array di byte è più veloce.
I risultati che ottengo sono un po ‘diversi ogni volta a causa del caching e di altri processi, ma ecco uno dei risultati che ho ottenuto (in millisecondi) con un file da 16 MB:

 75 ReadLines 82 ReadLine 22 ReadAllBytes 23 Read 32K 21 Read 64K 27 Read 128K 

In generale, File.ReadLines dovrebbe essere leggermente più lento di un ciclo StreamReader.ReadLine . File.ReadAllBytes è più lento con file più grandi e getterà un’eccezione di memoria con file enormi. La dimensione del buffer predefinita per FileStream è 4K, ma sulla mia macchina 64K sembrava la più veloce.

  private static int countWithReadLines(string filePath) { int count = 0; var lines = File.ReadLines(filePath); foreach (var line in lines) count++; return count; } private static int countWithReadLine(string filePath) { int count = 0; using (var sr = new StreamReader(filePath)) while (sr.ReadLine() != null) count++; return count; } private static int countWithFileStream(string filePath, int bufferSize = 1024 * 4) { using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { int count = 0; byte[] array = new byte[bufferSize]; while (true) { int length = fs.Read(array, 0, bufferSize); for (int i = 0; i < length; i++) if(array[i] == 10) count++; if (length < bufferSize) return count; } } // end of using } 

e testato con:

 var path = "1234567890.txt"; Stopwatch sw; string s = ""; File.WriteAllLines(path, Enumerable.Repeat("1234567890abcd", 1024 * 1024 )); // 16MB (16 bytes per line) sw = Stopwatch.StartNew(); countWithReadLines(path) ; sw.Stop(); s += sw.ElapsedMilliseconds + " ReadLines \n"; sw = Stopwatch.StartNew(); countWithReadLine(path) ; sw.Stop(); s += sw.ElapsedMilliseconds + " ReadLine \n"; sw = Stopwatch.StartNew(); countWithReadAllBytes(path); sw.Stop(); s += sw.ElapsedMilliseconds + " ReadAllBytes \n"; sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 * 32); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 32K \n"; sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 * 64); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 64K \n"; sw = Stopwatch.StartNew(); countWithFileStream(path, 1024 *128); sw.Stop(); s += sw.ElapsedMilliseconds + " Read 128K \n"; MessageBox.Show(s);