¿Cuál es la forma más rápida de leer un archivo de texto línea por línea?

Quiero leer un archivo de texto línea por línea. Quería saber si lo estoy haciendo lo más eficientemente posible dentro del scope de cosas de .NET C #.

Esto es lo que bash hasta ahora:

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 } 

Para encontrar la forma más rápida de leer un archivo línea por línea, deberá hacer una evaluación comparativa. He hecho algunas pequeñas pruebas en mi computadora, pero no puede esperar que mis resultados se apliquen a su entorno.

Usando StreamReader.ReadLine

Este es básicamente tu método. Por algún motivo, establece el tamaño del búfer en el valor más pequeño posible (128). Aumentar esto en general boostá el rendimiento. El tamaño predeterminado es 1.024 y otras buenas opciones son 512 (el tamaño del sector en Windows) o 4.096 (el tamaño del clúster en NTFS). Deberá ejecutar un punto de referencia para determinar un tamaño de búfer óptimo. Un buffer más grande es, si no más rápido, al menos no más lento que un buffer más pequeño.

 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 } 

El constructor de FileStream permite especificar FileOptions . Por ejemplo, si está leyendo un archivo grande de forma secuencial de principio a fin, puede beneficiarse de FileOptions.SequentialScan . Una vez más, la evaluación comparativa es lo mejor que puede hacer.

Usando File.ReadLines

Esto es muy parecido a su propia solución, excepto que se implementa utilizando un StreamReader con un tamaño de búfer fijo de 1,024. En mi computadora, esto da como resultado un rendimiento ligeramente mejor en comparación con tu código con el tamaño de búfer de 128. Sin embargo, puedes obtener el mismo aumento de rendimiento si utilizas un tamaño de búfer más grande. Este método se implementa utilizando un bloque iterador y no consume memoria para todas las líneas.

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

Usando File.Read All Line

Esto es muy similar al método anterior, excepto que este método hace crecer una lista de cadenas utilizadas para crear la matriz devuelta de líneas, por lo que los requisitos de memoria son más altos. Sin embargo, devuelve String[] y no un IEnumerable que le permite acceder aleatoriamente a las líneas.

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

Usando String.Split

Este método es considerablemente más lento, al menos en archivos grandes (probados en un archivo de 511 KB), probablemente debido a la forma en que se implementa String.Split . También asigna una matriz para todas las líneas aumentando la memoria requerida en comparación con su solución.

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

Mi sugerencia es usar File.ReadLines porque es limpio y eficiente. Si necesita opciones especiales para compartir (por ejemplo, usa FileShare.ReadWrite ), puede usar su propio código, pero debe boost el tamaño del búfer.

Si está usando .NET 4, simplemente use File.ReadLines que lo hace todo por usted. Sospecho que es muy similar a la tuya, excepto que también puede usar FileOptions.SequentialScan y un buffer más grande (128 parece muy pequeño).

Si bien File.ReadAllLines() es una de las formas más sencillas de leer un archivo, también es una de las más lentas.

Si solo quiere leer líneas en un archivo sin hacer mucho, de acuerdo con estos puntos de referencia , la forma más rápida de leer un archivo es el antiguo método de:

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

Sin embargo, si tiene que hacer mucho con cada línea, este artículo concluye que la mejor manera es la siguiente (y es más rápido asignar previamente una cadena [] si sabe cuántas líneas va a leer):

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

Usa el siguiente código:

 var lines = File.ReadAllLines(fileName); foreach (var line in lines) 

Esta fue una gran diferencia en el rendimiento de lectura.

Viene a costa del consumo de memoria, ¡pero merece la pena!

Hay un buen tema sobre esto en la pregunta sobre el desbordamiento de la stack. ¿El retorno de rendimiento es más lento que el rendimiento de la “vieja escuela”? .

Dice:

ReadAllLines carga todas las líneas en la memoria y devuelve una cadena []. Todo bien si el archivo es pequeño. Si el archivo es más grande de lo que cabe en la memoria, se quedará sin memoria.

ReadLines, por otro lado, utiliza el retorno de rendimiento para devolver una línea a la vez. Con él, puedes leer cualquier archivo de tamaño. No carga todo el archivo en la memoria.

Supongamos que quiere encontrar la primera línea que contiene la palabra “foo” y luego salir. Usando ReadAllLines, tendrías que leer todo el archivo en la memoria, incluso si aparece “foo” en la primera línea. Con ReadLines, solo lee una línea. ¿Cuál sería más rápido?

Si el tamaño del archivo no es grande, es más rápido leer todo el archivo y luego dividir la cadena:

 var filestreams = sr.ReadToEnd().Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); 

Si apunta a la velocidad de ejecución, sí lo está. El código podría ser más corto usando el constructor StreamReader.

Si tiene suficiente memoria, he encontrado algunas mejoras de rendimiento al leer todo el archivo en una secuencia de memoria , y luego abrir un lector de flujo para leer las líneas. Mientras planees leer todo el archivo de todos modos, esto puede arrojar algunas mejoras.

No puede obtener más rápido si desea utilizar una API existente para leer las líneas. Pero leer fragmentos más grandes y encontrar manualmente cada nueva línea en el búfer de lectura probablemente sería más rápido.