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

319

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

Esto es lo que estoy intentando 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
}
Loren C Fortner
fuente
77
¿ FastestQuieres decir desde las perspectivas de desempeño o desarrollo?
Todo el
1
Esto va a bloquear el archivo mientras dure el método. Puede usar File.ReadAllLines en una matriz y luego procesar la matriz.
Kell
17
Por cierto, incluya filestream = new FileStreamen using()la declaración para evitar posibles problemas molestos con mango de archivo bloqueado
SLL
Con respecto a la inclusión de FileStream usando la declaración (), vea StackOverflow con respecto al método recomendado: StackOverflow usando la declaración filestream streamreader
deegee
Creo que ReadToEnd () es más rápido.
Dan Gifford

Respuestas:

315

Para encontrar la forma más rápida de leer un archivo línea por línea, tendrá que hacer algunos puntos de referencia. He realizado 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 alguna razón, establece el tamaño del búfer en el valor más pequeño posible (128). Aumentar esto en general aumentará 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). Tendrá que ejecutar un punto de referencia para determinar un tamaño de búfer óptimo. Un búfer más grande es, si no más rápido, al menos no más lento que un búfer 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 FileStreamconstructor le permite especificar FileOptions . Por ejemplo, si está leyendo un archivo grande secuencialmente de principio a fin, puede beneficiarse 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 StreamReadertamaño de búfer fijo de 1.024. En mi computadora, esto resulta en un rendimiento ligeramente mejor en comparación con su código con un tamaño de búfer de 128. Sin embargo, puede obtener el mismo aumento de rendimiento utilizando 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.ReadAllLines

Esto es muy similar al método anterior, excepto que este método hace crecer una lista de cadenas utilizadas para crear la matriz de líneas devuelta, por lo que los requisitos de memoria son mayores. Sin embargo, vuelveString[] y no le IEnumerable<String>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 (probado en un archivo de 511 KB), probablemente debido a cómo String.Splitse implementa. 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.ReadLinesporque es limpio y eficiente. Si necesita opciones especiales para compartir (por ejemplo, las que usa FileShare.ReadWrite), puede usar su propio código, pero debe aumentar el tamaño del búfer.

Martin Liversage
fuente
1
Gracias por esto: su inclusión del parámetro de tamaño de búfer en el constructor de StreamReader fue realmente útil. Estoy transmitiendo desde la API S3 de Amazon, y el uso de un tamaño de búfer coincidente acelera considerablemente las cosas junto con ReadLine ().
Richard K.
No entiendo. En teoría, la gran mayoría del tiempo dedicado a leer el archivo sería el tiempo de búsqueda en el disco y los gastos generales de las secuencias de manipulación, como lo que haría con File.ReadLines. File.ReadLines, por otro lado, se supone que debe leer todo el archivo en la memoria de una vez. ¿Cómo podría ser peor en el rendimiento?
h9uest
2
No puedo decir sobre el rendimiento de la velocidad, pero una cosa es segura: es mucho peor en el consumo de memoria. Si tiene que manejar archivos muy grandes (GB, por ejemplo), esto es muy crítico. Aún más si eso significa que tiene que intercambiar memoria. En el lado de la velocidad, puede agregar que ReadAllLine necesita leer TODAS las líneas ANTES de devolver el resultado que retrasa el procesamiento. En algunos escenarios, la IMPRESIÓN de la velocidad es más importante que la velocidad bruta.
bkqc
Si lee la secuencia como conjuntos de bytes, leerá el archivo de 20% ~ 80% más rápido (de las pruebas que hice). Lo que necesita es obtener la matriz de bytes y convertirla en cadena. Así es como lo hice: para leer, use stream.Read () Puede hacer un bucle para que se lea en fragmentos. Después de agregar todo el contenido en una matriz de bytes (use System.Buffer.BlockCopy ) deberá convertir los bytes en una cadena: Encoding.Default.GetString (byteContent, 0, byteContent.Length - 1) .Split (nueva cadena [ ] {"\ r \ n", "\ r", "\ n"}, StringSplitOptions.None);
Kim Lage
200

Si está utilizando .NET 4, simplemente use File.ReadLinescuál lo hace todo por usted. Sospecho que es muy similar al tuyo, excepto que también puede usar FileOptions.SequentialScany un búfer más grande (128 parece muy pequeño).

Jon Skeet
fuente
Otro beneficio ReadLines()es que es perezoso, por lo que funciona bien con LINQ.
stt106
35

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

Si solo desea 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 método antiguo 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 preasignar 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
});
Free Coder 24
fuente
13

Utiliza el siguiente código:

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

Esta fue una gran diferencia en el rendimiento de lectura.

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

usuario2671536
fuente
preferiría File.ReadLines (haga clic en mí) queFile.ReadAllLines
newbieguy
5

Hay un buen tema sobre esto en la pregunta de desbordamiento de pila. ¿ Es más lento el "rendimiento de rendimiento" 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, usa el retorno de rendimiento para devolver una línea a la vez. Con él, puede leer archivos de cualquier tamaño. No carga todo el archivo en la memoria.

Digamos que desea encontrar la primera línea que contiene la palabra "foo", y luego salga. Con ReadAllLines, tendría 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?

Marcel James
fuente
4

Si el tamaño del archivo no es grande, entonces es más rápido leer todo el archivo y luego dividirlo

var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                              StringSplitOptions.RemoveEmptyEntries);
Saeed Amiri
fuente
66
File.ReadAllLines()
jgauffin el
@jgauffin No sé detrás de la implementación de file.ReadAlllines () pero creo que tiene un búfer limitado y el búfer fileReadtoEnd debería ser mayor, por lo que el número de acceso al archivo se reducirá de esta manera y haciendo string.Split en el el tamaño del archivo del caso no es grande, es más rápido que el acceso múltiple al archivo.
Saeed Amiri
Dudo que File.ReadAllLinestenga un tamaño de búfer fijo ya que se conoce el tamaño del archivo.
jgauffin
1
@jgauffin: en .NET 4.0 File.ReadAllLinescrea una lista y se agrega a esta lista en un bucle usando StreamReader.ReadLine(con una posible reasignación de la matriz subyacente). Este método utiliza un tamaño de búfer predeterminado de 1024. StreamReader.ReadToEndEvita la parte de análisis de línea y el tamaño del búfer se puede establecer en el constructor si lo desea.
Martin Liversage
Sería útil definir "GRANDE" con respecto al tamaño del archivo.
Paul
2

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 secuencia para leer las líneas. Siempre y cuando planee leer todo el archivo de todos modos, esto puede producir algunas mejoras.

Kibbee
fuente
1
File.ReadAllLinesParece ser una mejor opción entonces.
jgauffin el
2

No puede ser 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.

jgauffin
fuente