Tengo la hermosa tarea de averiguar cómo manejar archivos grandes que se cargan en el editor de scripts de nuestra aplicación (es como VBA para nuestro producto interno para macros rápidas). La mayoría de los archivos pesan alrededor de 300-400 KB, lo cual es una carga adecuada. Pero cuando superan los 100 MB, el proceso tiene dificultades (como era de esperar).
Lo que sucede es que el archivo se lee y se inserta en un RichTextBox que luego se navega; no se preocupe demasiado por esta parte.
El desarrollador que escribió el código inicial simplemente usa un StreamReader y hace
[Reader].ReadToEnd()
que podría tardar bastante en completarse.
Mi tarea es dividir este fragmento de código, leerlo en trozos en un búfer y mostrar una barra de progreso con una opción para cancelarlo.
Algunas suposiciones:
- La mayoría de los archivos serán de 30 a 40 MB
- El contenido del archivo es texto (no binario), algunos son formato Unix, algunos son DOS.
- Una vez que se recupera el contenido, calculamos qué terminador se utiliza.
- A nadie le preocupa una vez que se ha cargado, el tiempo que se tarda en renderizar en el cuadro de texto enriquecido. Es solo la carga inicial del texto.
Ahora para las preguntas:
- ¿Puedo simplemente usar StreamReader, luego verificar la propiedad Length (es decir, ProgressMax) y emitir una lectura para un tamaño de búfer establecido e iterar en un ciclo while MIENTRAS dentro de un trabajador en segundo plano, para que no bloquee el hilo principal de la interfaz de usuario? Luego, regrese el generador de cadenas al hilo principal una vez que esté completo.
- Los contenidos irán a un StringBuilder. ¿Puedo inicializar StringBuilder con el tamaño de la secuencia si la longitud está disponible?
¿Son estas (en su opinión profesional) buenas ideas? He tenido algunos problemas en el pasado con la lectura de contenido de Streams, porque siempre se perderán los últimos bytes o algo así, pero haré otra pregunta si este es el caso.
fuente
Respuestas:
Puede mejorar la velocidad de lectura utilizando un BufferedStream, como este:
ACTUALIZACIÓN de marzo de 2013
Recientemente escribí código para leer y procesar (buscando texto en) archivos de texto de 1 GB (mucho más grandes que los archivos involucrados aquí) y logré una ganancia significativa de rendimiento mediante el uso de un patrón de productor / consumidor. La tarea de productor leyó líneas de texto usando el
BufferedStream
y las entregó a una tarea de consumidor separada que hizo la búsqueda.Usé esto como una oportunidad para aprender TPL Dataflow, que es muy adecuado para codificar rápidamente este patrón.
Por qué BufferedStream es más rápido
ACTUALIZACIÓN de diciembre de 2014: su millaje puede variar
Según los comentarios, FileStream debería usar un BufferedStream internamente. En el momento en que se proporcionó esta respuesta por primera vez, medí un aumento significativo del rendimiento al agregar un BufferedStream. En ese momento, estaba apuntando a .NET 3.x en una plataforma de 32 bits. Hoy, con el objetivo de .NET 4.5 en una plataforma de 64 bits, no veo ninguna mejora.
Relacionado
Me encontré con un caso en el que la transmisión de un archivo CSV grande y generado al flujo de respuesta desde una acción ASP.Net MVC fue muy lenta. Agregar un BufferedStream mejoró el rendimiento en 100 veces en este caso. Para obtener más información, consulte Salida sin búfer muy lenta
fuente
Si lee las estadísticas de rendimiento y de referencia en este sitio web , verá que la forma más rápida de leer (porque la lectura, la escritura y el procesamiento son todos diferentes) un archivo de texto es el siguiente fragmento de código:
Se evaluaron aproximadamente 9 métodos diferentes, pero ese parece adelantarse la mayor parte del tiempo, incluso superando al lector en búfer, como han mencionado otros lectores.
fuente
StringBuilder
para cargarlos en la memoria, se carga más rápido ya que no crea una nueva cadena cada vez que agrega caracteres)Dice que le han pedido que muestre una barra de progreso mientras se carga un archivo grande. ¿Se debe a que los usuarios realmente quieren ver el% exacto de carga de archivos, o simplemente porque quieren comentarios visuales de que algo está sucediendo?
Si esto último es cierto, entonces la solución se vuelve mucho más simple. Solo hazlo
reader.ReadToEnd()
en un hilo de fondo y muestra una barra de progreso tipo marquesina en lugar de una adecuada.Planteo este punto porque, en mi experiencia, este suele ser el caso. Cuando esté escribiendo un programa de procesamiento de datos, los usuarios definitivamente estarán interesados en una cifra% completa, pero para las actualizaciones de IU simples pero lentas, es más probable que solo quieran saber que la computadora no se ha bloqueado. :-)
fuente
StreamReader
ciclo. Sin embargo, seguirá siendo más sencillo porque no es necesario seguir leyendo para calcular el indicador de progreso.Para archivos binarios, la forma más rápida de leerlos que he encontrado es esta.
En mis pruebas es cientos de veces más rápido.
fuente
Utilice un trabajador en segundo plano y lea solo un número limitado de líneas. Leer más solo cuando el usuario se desplaza.
Y trate de nunca usar ReadToEnd (). Es una de las funciones que piensas "¿por qué lo hicieron?"; es un ayudante de script kiddies que va bien con cosas pequeñas, pero como ves, apesta para archivos grandes ...
Los tipos que le dicen que use StringBuilder deben leer MSDN con más frecuencia:
Consideraciones de rendimiento
Los métodos Concat y AppendFormat concatenan datos nuevos a un objeto String o StringBuilder existente. Una operación de concatenación de objetos String siempre crea un nuevo objeto a partir de la cadena existente y los nuevos datos. Un objeto StringBuilder mantiene un búfer para adaptarse a la concatenación de nuevos datos. Los nuevos datos se agregan al final del búfer si hay espacio disponible; de lo contrario, se asigna un nuevo búfer más grande, los datos del búfer original se copian en el nuevo búfer y luego los nuevos datos se añaden al nuevo búfer. El rendimiento de una operación de concatenación para un objeto String o StringBuilder depende de la frecuencia con la que se produce una asignación de memoria.
Una operación de concatenación de String siempre asigna memoria, mientras que una operación de concatenación de StringBuilder solo asigna memoria si el búfer del objeto StringBuilder es demasiado pequeño para acomodar los nuevos datos. En consecuencia, la clase String es preferible para una operación de concatenación si se concatenan un número fijo de objetos String. En ese caso, el compilador podría incluso combinar las operaciones de concatenación individuales en una sola operación. Es preferible un objeto StringBuilder para una operación de concatenación si se concatenan un número arbitrario de cadenas; por ejemplo, si un bucle concatena un número aleatorio de cadenas de entrada del usuario.
Eso significa una gran asignación de memoria, lo que se convierte en un gran uso del sistema de archivos de intercambio, que simula secciones de la unidad de disco duro para que actúen como la memoria RAM, pero una unidad de disco duro es muy lenta.
La opción StringBuilder se ve bien para quienes usan el sistema como un usuario único, pero cuando tiene dos o más usuarios leyendo archivos grandes al mismo tiempo, tiene un problema.
fuente
Esto debería ser suficiente para comenzar.
fuente
Eche un vistazo al siguiente fragmento de código. Has mencionado
Most files will be 30-40 MB
. Esto afirma leer 180 MB en 1.4 segundos en un Intel Quad Core:Artículo original
fuente
Es posible que sea mejor utilizar el manejo de archivos mapeados en memoria aquí . El soporte de archivos mapeados en memoria estará disponible en .NET 4 (creo ... lo escuché a través de alguien más hablando de ello), de ahí este contenedor que usa p / invoca para hacer el mismo trabajo ..
Editar: Vea aquí en MSDN cómo funciona, aquí está la entrada del blog que indica cómo se hace en el próximo .NET 4 cuando salga como lanzamiento. El enlace que he dado anteriormente es un envoltorio alrededor del pinvoke para lograr esto. Puede mapear todo el archivo en la memoria y verlo como una ventana deslizante cuando se desplaza por el archivo.
fuente
¡Todas excelentes respuestas! sin embargo, para alguien que busca una respuesta, estas parecen estar algo incompletas.
Como una cadena estándar solo puede tener un tamaño X, de 2 Gb a 4 Gb, según su configuración, estas respuestas realmente no cumplen con la pregunta del OP. Un método es trabajar con una lista de cadenas:
Algunos pueden querer tokenizar y dividir la línea al procesar. La lista de cadenas ahora puede contener grandes volúmenes de texto.
fuente
Un iterador puede ser perfecto para este tipo de trabajo:
Puede llamarlo usando lo siguiente:
A medida que se carga el archivo, el iterador devolverá el número de progreso de 0 a 100, que puede usar para actualizar su barra de progreso. Una vez finalizado el ciclo, StringBuilder contendrá el contenido del archivo de texto.
Además, debido a que desea texto, podemos usar BinaryReader para leer caracteres, lo que garantizará que sus búferes se alineen correctamente al leer cualquier carácter de varios bytes ( UTF-8 , UTF-16 , etc.).
Todo esto se hace sin utilizar tareas en segundo plano, subprocesos o complejas máquinas de estado personalizadas.
fuente
Mi archivo tiene más de 13 GB:
El siguiente enlace contiene el código que lee un fragmento de archivo fácilmente:
Leer un archivo de texto grande
Más información
fuente