2011-04-16 20 views
7

Básicamente lo que me gustaría hacer es ejecutar varias (15-25) regex reemplaza en una sola cadena con la mejor gestión de memoria posible.C# Multiple Regex reemplaza en cadena - Demasiada memoria

Descripción general: Transmite un archivo de solo texto (a veces html) a través de ftp que se agrega a StringBuilder para obtener una cadena muy grande. El tamaño del archivo oscila entre 300 KB y 30 MB.

Las expresiones regulares son semi-complejas, pero requieren múltiples líneas del archivo (identificando secciones de un libro, por ejemplo), así que romper arbitrariamente la cadena o ejecutar el reemplazo en cada ciclo de descarga está fuera de la respuesta.

Una muestra de reemplazar:

Regex re = new Regex("<A.*?>Table of Contents</A>", RegexOptions.IgnoreCase); 
source = re.Replace(source, ""); 

Con cada ejecución de un reemplazar los cohetes cielo memoria, sé que esto se debe a String son inmutables en C# y tiene que hacer una copia - incluso si llamo GC.Collect() se todavía no ayuda lo suficiente para un archivo de 30 MB.

Cualquier consejo sobre una mejor manera de acercarse, o una forma de realizar múltiples regex reemplaza usando memoria constante (hacer 2 copias (tan 60MB en memoria), realizar búsqueda, descartar copia de nuevo a 30MB)?

Actualización:

No parece ser una respuesta sencilla, pero para la gente del futuro mirando este Terminé usando una combinación de todas las respuestas a continuación para llegar a un estado aceptable:

  1. Si es posible, divida la cadena en fragmentos, consulte la respuesta de manojs para ver cómo se está leyendo el archivo, buscando puntos finales adecuados.

  2. Si no puede dividir como se transmite, al menos divídalo más tarde si es posible; consulte la respuesta de ChrisWue para algunas herramientas externas que pueden ayudar con este proceso para canalizar archivos.

  3. Optimice la expresión regular, evite los operadores codiciosos y trate de limitar lo que el motor tiene que hacer tanto como sea posible - vea la respuesta de Sylverdrag.

  4. Combine la expresión regular cuando sea posible, esto reduce el número de reemplazos para cuando las expresiones regulares no se basan entre sí (útil en este caso para limpiar entradas incorrectas) - vea la respuesta de Brian Reichle para una muestra de código.

¡Gracias a todos!

+0

Llamo a cada expresión regular una vez en la cadena, ¿compilaré ayuda con la cantidad de veces que hace un reemplazo? Al igual que en la expresión regular de muestra, si había 500 coincidencias de tabla de contenido para reemplazar, ¿se ejecutaría una versión compilada más rápido? – WSkid

+0

Lo siento, me di cuenta de mi error y eliminé mi comentario, pero ya me había respondido. Sí, la compilación puede no brindar beneficios para usted. – manojlds

+0

No estoy seguro de si el requisito le permite analizar una línea de archivo. Si una línea en el archivo se puede considerar de forma independiente, entonces le sugiero que analice cada línea de archivo (CPU vs memoria ??) en lugar de todo el archivo en la memoria. Los ciclos/tiempo de la CPU pueden aumentar, pero creo que la memoria utilizada se reducirá. Puedes darle una oportunidad. –

Respuesta

2

Dependiendo de la naturaleza de de la expresión regular, es posible que ser capaz de combinarlos en una única expresión regular y usar la sobrecarga de Reemplazar() que toma un delegado MatchEvaluator para determinar el reemplazo de la cadena coincidente.

Regex re = new Regex("First Pattern|Second Pattern|Super(Mega)*Delux", RegexOptions.IgnoreCase); 

source = re.Replace(source, delegate(Match m) 
{ 
    string value = m.Value; 

    if(value.Equals("first pattern", StringComparison.OrdinalIgnoreCase) 
    { 
     return "1st"; 
    } 
    else if(value.Equals("second pattern", StringComparison.OrdinalIgnoreCase) 
    { 
     return "2nd"; 
    } 
    else 
    { 
     return ""; 
    } 
}); 

Por supuesto, esto se derrumba si los últimos patrones deben poder coincidir en el resultado de reemplazos anteriores.

+0

Sí, lo siento, olvidé agregar que algunos reemplazos se basan en otros reemplazos. Sin embargo, usando este método pude combinar varios y pude bajar a unos 5 Reemplazar llamadas - ¡gracias! – WSkid

2

Tener un vistazo a este post que habla de buscar una corriente el uso de expresiones regulares en lugar de tener que almacenar en una cadena que consume memoria:

http://www.developer.com/design/article.php/3719741/Building-a-Regular-Expression-Stream-Search-with-the-NET-Framework.htm

+0

Sin duda es una lectura interesante, pero no ayudará si el patrón no tiene un límite superior fijo en la longitud. La única forma de evitar esto es pensar en mantener el "estado" al final de escanear una cadena cuando comience la siguiente. Pero también necesitaría aferrarse a las "páginas" anteriores que podrían coincidir. –

+0

Concur con Brian, no acaba de llegar hasta el final, pero creo que usaré esta técnica combinada con varias de las respuestas para que tenga las mejores oportunidades para usar este método y guardarlo en los archivos mencionado en la respuesta de ChrisWue. Con su respuesta Brian trae todo a un estado mucho más manejable. – WSkid

1

Suponiendo que los documentos se cargan tienen alguna tipo de estructura que puede ser mejor escribir un analizador para poner el documento en una forma estucturadas, rompiendo la cadena grande en varios trozos, y luego operan en esa estructura.

Un problema con una cadena grande es que los objetos de más de 85,000 bytes se consideran objetos grandes y se colocan en el montón grande de objetos que no está compactado y puede llevar a situaciones inesperadas de falta de memoria.

Otra opción sería canalizarlo a través de una herramienta externa como sed o awk.

+0

Gracias por la segunda parte. Lléveme a este informe de error que dice que LOH está realmente fragmentado y tiene posibles errores OOM en .Net 2 y 3.5, pero es ligeramente mejor en 4.0: http://connect.microsoft.com/ VisualStudio/feedback/details/521147/large-object-heap-fragmentation-causes-outofmemoryexception – WSkid

2

Tengo una situación bastante similar.

Utilice la opción de compilación para la expresión regular:

Source = Regex.Replace(source, pattern, replace, RegexOptions.Compiled); 

Dependiendo de su situación, se puede hacer una gran diferencia en la velocidad.

No es una solución completa, especialmente para archivos de más de 3-4 Mb.

Si decide qué tipo de expresión regular se debería ejecutar (no es mi caso), probablemente debería optimizar la expresión regular tanto como sea posible, evitando las operaciones costosas. Por ejemplo, evite operadores no codiciosos, evite mirar hacia atrás y mirar hacia atrás.

En lugar de utilizar:

<a.*?>xxx 

uso

<a[^<>]*>xxx 

La razón es que un operador ungreedy obliga al motor de expresiones regulares para comprobar todos y cada personaje en comparación con el resto de la expresión, mientras que [^ <>] solo requiere comparar el carácter actual a < y> y se detiene tan pronto como se cumple la condición. En un archivo grande, esto puede marcar la diferencia entre medio segundo y la congelación de una aplicación.

No resuelve el problema por completo, pero debería ayudar.

+0

Ese segundo tidbit ayuda mucho, y ahora, al mirar hacia atrás, se trata de una optimización extremadamente obvia: de las pruebas preliminares se cambian unas 8 de las no seguras. Star to the not <> reduce el tiempo de cálculo en unos 2 minutos en un anterior. 10 minutos de trabajo. – WSkid

+0

@WSkid: 2 minutos menos no está nada mal. ¿Intentó compilar también? Con archivos grandes, debería estar dando sus frutos. Hace una gran diferencia aquí. – Sylverdrag