String.Replace () vs. StringBuilder.Replace ()

Tengo una cadena en la que necesito reemplazar marcadores con valores de un diccionario. Tiene que ser lo más eficiente posible. Hacer un loop con un string.replace va a consumir memoria (las cadenas son inmutables, recuerda). ¿Sería mejor StringBuilder.Replace () ya que está diseñado para trabajar con manipulaciones de cadenas?

Esperaba evitar el gasto de RegEx, pero si eso va a ser más eficiente entonces que así sea.

Nota: No me importa la complejidad del código, solo qué tan rápido se ejecuta y la memoria que consume.

Estadísticas promedio: 255-1024 caracteres de longitud, 15-30 teclas en el diccionario.

Usando RedGate Profiler usando el siguiente código

class Program { static string data = ""; static Dictionary values; static void Main(string[] args) { Console.WriteLine("Data length: " + data.Length); values = new Dictionary() { { "ab", "aa" }, { "jk", "jj" }, { "lm", "ll" }, { "yz", "zz" }, { "ef", "ff" }, { "st", "uu" }, { "op", "pp" }, { "x", "y" } }; StringReplace(data); StringBuilderReplace1(data); StringBuilderReplace2(new StringBuilder(data, data.Length * 2)); Console.ReadKey(); } private static void StringReplace(string data) { foreach(string k in values.Keys) { data = data.Replace(k, values[k]); } } private static void StringBuilderReplace1(string data) { StringBuilder sb = new StringBuilder(data, data.Length * 2); foreach (string k in values.Keys) { sb.Replace(k, values[k]); } } private static void StringBuilderReplace2(StringBuilder data) { foreach (string k in values.Keys) { data.Replace(k, values[k]); } } } 
  • String.Replace = 5.843ms
  • StringBuilder.Replace # 1 = 4.059ms
  • Stringbuilder.Replace # 2 = 0.461ms

Longitud de cadena = 1456

stringbuilder # 1 crea el generador de cuerdas en el método, mientras que el # 2 no lo hace, por lo que la diferencia de rendimiento terminará siendo la misma, ya que simplemente estás eliminando ese trabajo del método. Si comienza con un generador de cuerdas en lugar de una cadena, entonces el # 2 podría ser el camino a seguir.

En cuanto a la memoria, al utilizar el generador de perfiles RedGateMemory, no hay nada de qué preocuparse hasta que entre en MUCHAS operaciones de reemplazo en las que el generador de cadenas va a ganar en general.

Esto puede ser de ayuda:

http://blogs.msdn.com/b/debuggingtoolbox/archive/2008/04/02/comparing-regex-replace-string-replace-and-stringbuilder-replace-which-has-better-performance.aspx

La respuesta corta parece ser que String.Replace es más rápido, aunque puede tener un mayor impacto en la huella de memoria / recolección de basura.

Sí, StringBuilder le dará tanto ganancia en velocidad como en memoria (básicamente porque no creará una instancia de una cadena cada vez que haga una manipulación con ella – StringBuilder siempre opera con el mismo objeto). Aquí hay un enlace de MSDN con algunos detalles.

Sería stringbuilder.replace ser mejor [que String.Replace]

Sí, mucho mejor. Y si puede estimar un límite superior para la nueva cadena (parece que puede), entonces probablemente sea lo suficientemente rápido.

Cuando lo creas así:

  var sb = new StringBuilder(inputString, pessimisticEstimate); 

entonces StringBuilder no tendrá que volver a asignar su búfer.

Convertir datos de un String a un StringBuilder y viceversa tomará un tiempo. Si uno solo está realizando una única operación de reemplazo, este tiempo puede no ser recuperado por las mejoras de eficiencia inherentes a StringBuilder. Por otro lado, si uno convierte una cadena a un StringBuilder, luego realiza muchas operaciones de Reemplazar y la convierte de nuevo al final, el enfoque de StringBuilder tiende a ser más rápido.

En lugar de ejecutar 15-30 reemplazar operaciones en toda la cadena, podría ser más eficiente usar algo así como una estructura de datos trie para contener su diccionario. Luego puede recorrer la cadena de entrada una vez para hacer toda su búsqueda / reemplazo.

Dependerá mucho de cuántos marcadores estén presentes en una cadena dada en promedio.

Es probable que la búsqueda de una clave sea similar entre StringBuilder y String, pero StringBuilder ganará si tiene que reemplazar muchos marcadores en una sola cadena.

Si solo espera uno o dos marcadores por cadena en promedio, y su diccionario es pequeño, simplemente iría por String.Replace.

Si hay muchos marcadores, es posible que desee definir una syntax personalizada para identificar los marcadores, por ejemplo, incluir corchetes con una regla de escape adecuada para un corchete literal. A continuación, puede implementar un algoritmo de análisis que itera por los caracteres de la cadena una vez, reconociendo y reemplazando cada marcador que encuentra. O usa una expresión regular.

Mis dos centavos aquí, simplemente escribí un par de líneas de código para probar cómo funciona cada método y, como esperaba, el resultado es “depende”.

Para cadenas más largas Regex parece funcionar mejor, para cadenas más cortas, String.Replace it is. Puedo ver que el uso de StringBuilder.Replace no es muy útil, y si se usa incorrectamente, podría ser letal en la perspectiva de GC (traté de compartir una instancia de StringBuilder ).

Verifique mi repo de GitHub de StringReplaceTests .

El problema con la respuesta de @DustinDavis es que opera recursivamente en la misma cadena. A menos que planee realizar un tipo de manipulación de ida y vuelta, en este tipo de prueba debería tener objetos separados para cada caso de manipulación.

Decidí crear mi propia prueba porque encontré algunas respuestas contradictorias en toda la Web, y quería estar completamente seguro. El progtwig en el que estoy trabajando se ocupa de una gran cantidad de texto (archivos con decenas de miles de líneas en algunos casos).

Así que aquí hay un método rápido que puedes copiar y pegar y ver por ti mismo, que es más rápido. Puede que tenga que crear su propio archivo de texto para probar, pero puede copiar y pegar fácilmente texto desde cualquier lugar y crear un archivo suficientemente grande para usted:

 using System; using System.Diagnostics; using System.IO; using System.Text; using System.Windows; void StringReplace_vs_StringBuilderReplace( string file, string word1, string word2 ) { using( FileStream fileStream = new FileStream( file, FileMode.Open, FileAccess.Read ) ) using( StreamReader streamReader = new StreamReader( fileStream, Encoding.UTF8 ) ) { string text = streamReader.ReadToEnd(), @string = text; StringBuilder @StringBuilder = new StringBuilder( text ); int iterations = 10000; Stopwatch watch1 = new Stopwatch.StartNew(); for( int i = 0; i < iterations; i++ ) if( i % 2 == 0 ) @string = @string.Replace( word1, word2 ); else @string = @string.Replace( word2, word1 ); watch1.Stop(); double stringMilliseconds = watch1.ElapsedMilliseconds; Stopwatch watch2 = new Stopwatch.StartNew(); for( int i = 0; i < iterations; i++ ) if( i % 2 == 0 ) @StringBuilder = @StringBuilder .Replace( word1, word2 ); else @StringBuilder = @StringBuilder .Replace( word2, word1 ); watch2.Stop(); double StringBuilderMilliseconds = watch1.ElapsedMilliseconds; MessageBox.Show( string.Format( "string.Replace: {0}\nStringBuilder.Replace: {1}", stringMilliseconds, StringBuilderMilliseconds ) ); } } 

Obtuve esa cadena. Reponer () fue más rápido en aproximadamente un 20% cada vez intercambiando palabras de 8 a 10 letras. Inténtalo por ti mismo si quieres tu propia evidencia empírica.