2010-08-17 11 views
19

Estoy escribiendo los contenidos de un archivo de texto en un StringBuilder y luego quiero realizar una serie de acciones de búsqueda/reemplazo en el texto contenido en el StringBuilder utilizando expresiones regulares.Reemplazos de Regex dentro de un StringBuilder

He tenido un problema ya que la función de reemplazo de StringBuilder no es capaz de aceptar argumentos de expresiones regulares.

Podría usar Regex.Replace en una cadena normal, pero estoy bajo la impresión de que esto es ineficaz debido al hecho de que se necesitarán crear dos copias de la cadena en la memoria ya que las cadenas .net son inmutables.

Una vez que haya actualizado el texto, pienso volver a escribirlo en el archivo original.

¿Cuál es la mejor y más eficiente manera de resolver mi problema?

EDITAR

Además de la respuesta (s) a continuación, he encontrado las siguientes preguntas que también arrojar alguna luz sobre mi problema -

Respuesta

23

La mejor y más eficiente solución para su tiempo es intentar el enfoque más simple primero: olvide el StringBuilder y simplemente use Regex.Replace. Luego descubra lo lento que es, bien puede ser lo suficientemente bueno. No olvide probar la expresión regular tanto en modo compilado como no compilado.

Si eso no es lo suficientemente rápido, considerar el uso de un StringBuilder para los reemplazos se puede expresar simplemente, y luego usar Regex.Replace para el resto. También podría considerar intentar combinar reemplazos, reduciendo el número de expresiones regulares (y, por lo tanto, cadenas intermedias) utilizadas.

+1

Estoy sorprendido de que no haya pensado en esto: en realidad ejecutarlo y verlo, en lugar de especular sobre cuál sería la velocidad. He borrado mi respuesta especulativa en consecuencia. – Timwi

+1

Si el Regex.Replace fue lo suficientemente rápido, ¿debería preocuparle en absoluto la gestión de la memoria? ¿Estoy analizando/optimizando las cosas al preocuparme por el exceso de memoria en la creación de múltiples cadenas? – ipr101

+0

Esto no es tanto una respuesta como una sugerencia. La pregunta es cómo hacer que Regex funcione con stringbuilder, y la respuesta es que no son compatibles a menos que usted escriba su propia implementación. Por qué este es el caso, no lo sé – Slight

1

No estoy seguro de si esto ayuda a su escenario o no, pero me encontré con algunos topes de consumo de memoria con Regex y necesitaba un método de extensión de comodín simple en un StringBuilder para superarlo. Si necesita una coincidencia Regex compleja y/o referencias inversas, esto no funcionará, pero si es simple * o? reemplazos comodín (con texto literal "reemplazar") sería hacer el trabajo para usted, entonces la solución al final de mi pregunta aquí debe, al menos, le dará un impulso:

Has anyone implemented a Regex and/or Xml parser around StringBuilders or Streams?

0

Aquí es un método de extensión se podría usar para lograr lo que quieres. Lleva un Dictionary donde la clave es el patrón que está buscando y el valor es con el que desea reemplazarlo. Aún crea copias de la cadena entrante, pero solo tiene que lidiar con esto una vez en lugar de crear copias para múltiples llamadas al Regex.Replace.

public static StringBuilder BulkReplace(this StringBuilder source, IDictionary<string, string> replacementMap) 
{ 
    if (source.Length == 0 || replacementMap.Count == 0) 
    { 
     return source; 
    } 
    string replaced = Regex.Replace(source.ToString(), String.Join("|", replacementMap.Keys.Select(Regex.Escape).ToArray()), m => replacementMap[m.Value], RegexOptions.IgnoreCase); 
    return source.Clear().Append(replaced); 
} 
+1

El objetivo de usar Regex con StringBuilder no es simplemente tener un método que haga el trabajo, sino minimizar el desperdicio de memoria, evitando en particular una gran cantidad de cadenas intermedias almacenadas en la memoria. –

+0

No es perfecto porque tiene que convertir el StringBuilder en una cadena, pero este método es aproximadamente 4 veces más rápido que simplemente llamar a Regex. Reemplazar una cadena una y otra vez. –

+0

Si el reemplazoMap contiene patrones, obtendrá el: "La clave dada no estaba presente en el diccionario". Esto es esperado ya que m.Value de replacementMap [m.Value] busca una clave que sea la cadena de actula que coincida con el patrón y no el patrón en sí. ¿Me estoy perdiendo de algo? Por patrones quiero decir cadenas de patrones regex como: "" <[^>] +> "y cadenas no exactas como"
mmmmmm

2

Usted tiene 3 opciones:

  1. hacer esto en una forma ineficiente con cuerdas como otros han recomendado aquí.

  2. Utilice la llamada .Matches() en su objeto Regex, y emule la forma en que .Replace() funciona (consulte el n. ° 3).

  3. adaptar la aplicación Mono de Regex para construir un Regex que acepta StringBuilder (y por favor, tú aquí!) Casi todo el trabajo ya está hecho para usted en Mono, pero tomará tiempo para suss las partes que hacer que funcione en su propia biblioteca. El Regex de Mono aprovecha la implementación JVM 2002 de Novell de Regex, por extraño que parezca.

En Mono:

System.Text.RegularExpressions.Regex utiliza un RxCompiler instanciar un IMachineFactory en forma de un RxInterpreterFactory, que era de esperar hace IMachine s como RxInterpreter s. Hacer que estos emitan es la mayor parte de lo que debe hacer, aunque si solo está buscando aprender cómo está todo estructurado para la eficiencia, es notable que gran parte de lo que está buscando está en su clase base, BaseMachine.

En particular, en BaseMachine es el material basado en StringBuilder. En el método LTRReplace, primero crea una instancia de StringBuilder con la cadena inicial, y todo a partir de allí es puramente basado en StringBuilder. En realidad, es muy molesto que Regex no tenga los métodos StringBuilder colgados, si suponemos que la implementación interna de Microsoft .Net es similar.

Dando vueltas de nuevo a la sugerencia 2, puede imitar el comportamiento LTRReplace 's llamando .Matches(), el seguimiento de dónde se encuentra en la cadena original, y bucles:

var matches = regex.Matches(original); 
var sb = new StringBuilder(original.Length); 
int pos = 0; // position in original string 
foreach(var match in matches) 
{ 
    sb.Append(original.Substring(pos, match.Index)); // Append the portion of the original we skipped 
    pos = match.Index; 

    // Make any operations you like on the match result, like your own custom Replace, or even run another Regex 

    pos += match.Value.Length; 
} 
sb.Append(original.Substring(pos, original.Length - 1)); 

embargo, esto sólo le ahorra algunos hilos - el El enfoque mod-Mono es el único que realmente lo hace bien.

Cuestiones relacionadas