c# - correct - String vs. StringBuilder
regarding string and stringbuilder it''s correct (24)
Entiendo la diferencia entre String
y StringBuilder
( StringBuilder
es mutable) pero ¿hay una gran diferencia de rendimiento entre los dos?
El programa en el que estoy trabajando tiene una gran cantidad de adjuntos de cadenas basadas en mayúsculas y minúsculas (500+). ¿Es el uso de StringBuilder
una mejor opción?
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 cadenas siempre asigna memoria, mientras que una operación de concatenación de cadenas solo asigna memoria si el búfer de objetos de 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 un número fijo de objetos String se concatenan. En ese caso, las compilaciones pueden incluso combinar las operaciones de concatenación individuales en una sola operación. Un objeto StringBuilder es preferible para una operación de concatenación si se concatena un número arbitrario de cadenas; por ejemplo, si un bucle concatena un número aleatorio de cadenas de entrada del usuario.
Fuente: MSDN
Además de las respuestas anteriores, lo primero que siempre hago cuando pienso en temas como este es crear una pequeña aplicación de prueba. Dentro de esta aplicación, realice una prueba de tiempo para ambos escenarios y vea por usted mismo cuál es más rápido.
En mi humilde opinión, agregar más de 500 entradas de cadena definitivamente debe usar StringBuilder.
Como regla general, si tengo que establecer el valor de la cadena más de una vez, o si hay algunos anexos a la cadena, entonces debe ser un generador de cadenas. He visto aplicaciones que escribí en el pasado antes de aprender acerca de los constructores de cuerdas que han tenido una enorme huella de memoria que parece seguir creciendo y creciendo. Cambiar estos programas para usar el generador de cadenas reduce significativamente el uso de la memoria. Ahora lo juro por el constructor de cuerdas.
Considere '' La triste tragedia del teatro de micro-optimización ''.
Creo que StringBuilder es más rápido si tiene más de 4 cadenas que necesita unir. Además, puede hacer algunas cosas geniales como AppendLine.
El uso de cadenas para la concatenación puede llevar a una complejidad de tiempo de ejecución del orden de O(n^2)
.
Si usa un StringBuilder
, hay mucho menos copiado de la memoria que debe hacerse. Con el StringBuilder(int capacity)
puede aumentar el rendimiento si puede estimar qué tan grande será el String
final. Incluso si no eres preciso, probablemente solo tengas que aumentar la capacidad de StringBuilder
un par de veces, lo que también puede ayudar al rendimiento.
En .NET, StringBuilder es aún más rápido que agregar cadenas. Estoy bastante seguro de que en Java, simplemente crean un StringBuffer debajo del capó cuando añades cadenas, así que no hay realmente una diferencia. No estoy seguro de por qué no han hecho esto en .NET todavía.
Este punto de referencia muestra que la concatenación regular es más rápida cuando se combinan 3 o menos cadenas.
http://www.chinhdo.com/20070224/stringbuilder-is-not-always-faster/
StringBuilder puede hacer una mejora muy significativa en el uso de la memoria, especialmente en el caso de agregar 500 cadenas juntas.
Considere el siguiente ejemplo:
string buffer = "The numbers are: ";
for( int i = 0; i < 5; i++)
{
buffer += i.ToString();
}
return buffer;
¿Qué pasa en la memoria? Se crean las siguientes cadenas:
1 - "The numbers are: "
2 - "0"
3 - "The numbers are: 0"
4 - "1"
5 - "The numbers are: 01"
6 - "2"
7 - "The numbers are: 012"
8 - "3"
9 - "The numbers are: 0123"
10 - "4"
11 - "The numbers are: 01234"
12 - "5"
13 - "The numbers are: 012345"
¡Al agregar esos cinco números al final de la cadena, creamos 13 objetos de cadena! ¡Y 12 de ellos fueron inútiles! ¡Guauu!
StringBuilder soluciona este problema. No es una "cadena mutable" como escuchamos a menudo ( todas las cadenas en .NET son inmutables ). Funciona manteniendo un búfer interno, una matriz de caracteres. Al llamar a Append () o AppendLine () se agrega la cadena al espacio vacío al final de la matriz de caracteres; si la matriz es demasiado pequeña, crea una nueva matriz más grande y copia el búfer allí. Por lo tanto, en el ejemplo anterior, StringBuilder podría necesitar solo una matriz para contener las 5 adiciones a la cadena, dependiendo del tamaño de su búfer. Puedes decirle a StringBuilder qué tan grande debe ser su búfer en el constructor.
He visto mejoras significativas en el rendimiento al usar el EnsureCapacity(int capacity)
en una instancia de StringBuilder
antes de usarlo para cualquier cadena de almacenamiento. Normalmente lo llamo en la línea de código después de la instanciación. Tiene el mismo efecto que si crease una instancia del StringBuilder
esta manera:
var sb = new StringBuilder(int capacity);
Esta llamada asigna la memoria necesaria con anticipación, lo que causa menos asignaciones de memoria durante varias operaciones de Append()
. Debe hacer una suposición informada sobre la cantidad de memoria que necesitará, pero para la mayoría de las aplicaciones esto no debería ser demasiado difícil. Normalmente me equivoco al lado de un poco demasiado de memoria (estamos hablando de 1k más o menos).
La concatenación de cuerdas te costará más. En Java, puede usar StringBuffer o StringBuilder según su necesidad. Si desea una implementación sincronizada y segura para subprocesos, vaya a StringBuffer. Esto será más rápido que la concatenación de cadenas.
Si no necesita una implementación segura o sincronizada con Thread, vaya a StringBuilder. Esto será más rápido que la concatenación de cadenas y también más rápido que StringBuffer, ya que no existe una sobrecarga de sincronización.
Mi enfoque siempre ha sido utilizar StringBuilder al concatenar 4 o más cadenas O cuando no sé cómo pueden tener lugar las concatenaciones.
No vayas ciegamente a StringBuilder. Hay escenarios donde StringBuilder no mejora el rendimiento.
Rico Mariani ha escrito dos entradas de blog perspicaces sobre esto:
Para aclarar lo que Gillian dijo acerca de 4 cuerdas, si tienes algo como esto:
string a,b,c,d;
a = b + c + d;
entonces sería más rápido usar cadenas y el operador más. Esto se debe a que (al igual que Java, como señala Eric), utiliza StringBuilder internamente automáticamente (en realidad, usa una primitiva que también usa StringBuilder)
Sin embargo, si lo que estás haciendo está más cerca de:
string a,b,c,d;
a = a + b;
a = a + c;
a = a + d;
Entonces necesitas usar explícitamente un StringBuilder. .Net no crea automáticamente un StringBuilder aquí, porque sería inútil. Al final de cada línea, "a" tiene que ser una cadena (inmutable), por lo que tendría que crear y disponer un StringBuilder en cada línea. Para la velocidad, deberías usar el mismo StringBuilder hasta que hayas terminado de construir:
string a,b,c,d;
StringBuilder e = new StringBuilder();
e.Append(b);
e.Append(c);
e.Append(d);
a = e.ToString();
Sí, StringBuilder
ofrece un mejor rendimiento al realizar operaciones repetidas en una cadena. Se debe a que todos los cambios se realizan en una sola instancia, por lo que puede ahorrar mucho tiempo en lugar de crear una nueva instancia como String
.
String Vs Stringbuilder
String
- bajo el espacio de nombres del
System
- instancia inmutable (solo lectura)
- El rendimiento se degrada cuando ocurre un cambio continuo de valor
- a salvo de amenazas
- bajo el espacio de nombres del
StringBuilder
(cadena mutable)- bajo
System.Text
namespace - instancia mutable
- muestra un mejor rendimiento ya que se realizan nuevos cambios en la instancia existente
- bajo
Recomiendo encarecidamente el artículo dotnet mob: String Vs StringBuilder en C # .
Pregunta relacionada sobre el desbordamiento de pila: ¿ Mutabilidad de la cadena cuando la cadena no cambia en C #? .
Sí, la diferencia de rendimiento es significativa. Consulte el artículo de la KB " Cómo mejorar el rendimiento de concatenación de cadenas en Visual C # ".
Siempre he intentado codificar la claridad primero y luego optimizar el rendimiento más tarde. ¡Eso es mucho más fácil que hacerlo al revés! Sin embargo, habiendo visto la enorme diferencia de rendimiento en mis aplicaciones entre las dos, ahora lo pienso con un poco más de cuidado.
Afortunadamente, es relativamente sencillo ejecutar el análisis de rendimiento en su código para ver dónde está gastando el tiempo y luego modificarlo para usar StringBuilder
donde sea necesario.
Si estás haciendo mucha concatenación de cadenas, usa un StringBuilder. Cuando concatena con una Cadena, crea una Cadena nueva cada vez, usando más memoria.
Alex
String y StringBuilder son en realidad inmutables, StringBuilder ha incorporado buffers que permiten que su tamaño sea administrado de manera más eficiente. Cuando StringBuilder necesita cambiar de tamaño es cuando se vuelve a asignar en el montón. Por defecto, tiene un tamaño de 16 caracteres, puede configurarlo en el constructor.
p.ej.
StringBuilder sb = new StringBuilder (50);
StringBuilder es preferible SI está haciendo varios bucles, o forks en su paso de código ... sin embargo, para un rendimiento PURE, si puede salirse con una declaración de cadena SINGLE , entonces eso es mucho más eficaz.
Por ejemplo:
string myString = "Some stuff" + var1 + " more stuff"
+ var2 + " other stuff" .... etc... etc...;
es más performante que
StringBuilder sb = new StringBuilder();
sb.Append("Some Stuff");
sb.Append(var1);
sb.Append(" more stuff");
sb.Append(var2);
sb.Append("other stuff");
// etc.. etc.. etc..
En este caso, StringBuild podría considerarse más mantenible, pero no es más eficaz que la declaración de cadena única.
9 de cada 10 veces ... use el generador de cadenas.
En una nota al margen: string + var también es más eficaz que el enfoque de string.Format (generalmente) que utiliza un StringBuilder internamente (en caso de duda ... ¡verifique el reflector!)
StringBuilder es probablemente preferible. El motivo es que asigna más espacio del que se necesita actualmente (se establece el número de caracteres) para dejar espacio para futuros anexos. Entonces, esos apéndices futuros que caben en el búfer actual no requieren ninguna asignación de memoria o recolección de basura, lo que puede ser costoso. En general, uso StringBuilder para la concatentación de cadenas complejas o el formato múltiple, luego me convierto en una cadena normal cuando los datos están completos, y quiero un objeto inmutable de nuevo.
StringBuilder funcionará mejor, desde un punto de vista de memoria. En cuanto al procesamiento, la diferencia en el tiempo de ejecución puede ser despreciable.
StringBuilder reduce el número de asignaciones y asignaciones, a un costo de memoria extra utilizada. Si se usa correctamente, puede eliminar completamente la necesidad de que el compilador asigne cadenas cada vez más grandes una y otra vez hasta que se encuentre el resultado.
string result = "";
for(int i = 0; i != N; ++i)
{
result = result + i.ToString(); // allocates a new string, then assigns it to result, which gets repeated N times
}
contra
String result;
StringBuilder sb = new StringBuilder(10000); // create a buffer of 10k
for(int i = 0; i != N; ++i)
{
sb.Append(i.ToString()); // fill the buffer, resizing if it overflows the buffer
}
result = sb.ToString(); // assigns once
Un ejemplo simple para demostrar la diferencia de velocidad cuando se utiliza String
concatenación de String
frente a StringBuilder
:
System.Diagnostics.Stopwatch time = new Stopwatch();
string test = string.Empty;
time.Start();
for (int i = 0; i < 100000; i++)
{
test += i;
}
time.Stop();
System.Console.WriteLine("Using String concatenation: " + time.ElapsedMilliseconds + " milliseconds");
Resultado:
Usando la concatenación de cadenas: 15423 milisegundos
StringBuilder test1 = new StringBuilder();
time.Reset();
time.Start();
for (int i = 0; i < 100000; i++)
{
test1.Append(i);
}
time.Stop();
System.Console.WriteLine("Using StringBuilder: " + time.ElapsedMilliseconds + " milliseconds");
Resultado:
Utilizando StringBuilder: 10 milisegundos
Como resultado, la primera iteración tomó 15423 ms, mientras que la segunda iteración con StringBuilder
tomó 10 ms.
Me parece que el uso de StringBuilder
es más rápido, mucho más rápido.
StringBuilder
es mejor para construir una cadena a partir de muchos valores no constantes.
Si está creando una cadena a partir de una gran cantidad de valores constantes, como varias líneas de valores en un documento HTML o XML u otros trozos de texto, puede simplemente agregar la misma cadena, porque casi todos los compiladores sí lo hacen. "plegado constante", un proceso de reducción del árbol de análisis cuando tienes un montón de manipulación constante (también se usa cuando escribes algo como int minutesPerYear = 24 * 365 * 60
). Y para casos simples con valores no constantes anexados entre sí, el compilador .NET reducirá su código a algo similar a lo que hace StringBuilder
.
Pero cuando el apilador no puede reducir su apéndice a algo más simple, querrá un StringBuilder
. Como señala fizch, es más probable que ocurra dentro de un bucle.
StringBuilder
es significativamente más eficiente, pero no verá ese rendimiento a menos que esté realizando una gran cantidad de modificación de cadena.
A continuación se muestra un fragmento rápido de código para dar un ejemplo del rendimiento. Como puede ver, en realidad solo comienza a ver un aumento importante en el rendimiento cuando entra en grandes iteraciones.
Como puede ver, las 200,000 iteraciones tomaron 22 segundos, mientras que las 1 millón de iteraciones que usaron el StringBuilder
fueron casi instantáneas.
string s = string.Empty;
StringBuilder sb = new StringBuilder();
Console.WriteLine("Beginning String + at " + DateTime.Now.ToString());
for (int i = 0; i <= 50000; i++)
{
s = s + ''A'';
}
Console.WriteLine("Finished String + at " + DateTime.Now.ToString());
Console.WriteLine();
Console.WriteLine("Beginning String + at " + DateTime.Now.ToString());
for (int i = 0; i <= 200000; i++)
{
s = s + ''A'';
}
Console.WriteLine("Finished String + at " + DateTime.Now.ToString());
Console.WriteLine();
Console.WriteLine("Beginning Sb append at " + DateTime.Now.ToString());
for (int i = 0; i <= 1000000; i++)
{
sb.Append("A");
}
Console.WriteLine("Finished Sb append at " + DateTime.Now.ToString());
Console.ReadLine();
Resultado del código anterior:
Cadena de inicio + al 28/01/2013 16:55:40.
Cadena terminada + al 28/01/2013 16:55:40.
Cadena de inicio + al 28/01/2013 16:55:40.
Cadena terminada + al 28/01/2013 16:56:02.
A partir de la Sb anexa el 28/01/2013 16:56:02.
Terminado Sb anexar el 28/01/2013 16:56:02.