¿Cuándo usar StringBuilder?

82

Entiendo los beneficios de StringBuilder.

Pero si quiero concatenar 2 cadenas, supongo que es mejor (más rápido) hacerlo sin StringBuilder. ¿Es esto correcto?

¿En qué punto (número de cadenas) es mejor utilizar StringBuilder?

Shiraz Bhaiji
fuente
1
Creo que esto se ha cubierto anteriormente.
Mark Schultheiss
posible duplicado de stackoverflow.com/questions/529999/when-to-use-stringbuilder
Jørn Schou-Rode

Respuestas:

79

Le sugiero encarecidamente que lea La triste tragedia del teatro de microoptimización , de Jeff Atwood.

Trata la concatenación simple frente a StringBuilder frente a otros métodos.

Ahora, si quieres ver algunos números y gráficos, sigue el enlace;)

Alex Bagnolini
fuente
+1 por si ! El tiempo que se dedica a preocuparse por esto es el tiempo que se dedica a no hacer algo que realmente podría importar.
Greg D
8
Sin embargo, su lectura es incorrecta: no importa en muchos casos, cuando no hay bucles involucrados, en otros casos, sin embargo, puede importar MUCHO
Peter
1
Eliminé la edición porque solo contenía información incorrecta en una respuesta aceptada.
Peter
2
y solo para mostrar cuánto importa, del artículo al que te refieres: "En la mayoría de los lenguajes de recolección de basura, las cadenas son inmutables: cuando agregas dos cadenas, el contenido de ambas se copia. A medida que sigues agregando, el resultado en este bucle , se asigna más y más memoria cada vez. Esto conduce directamente a un rendimiento n2 cuadrádico terrible "
Peter
1
¿Por qué esta es la respuesta aceptada? No creo que simplemente soltar un enlace y decir "lee esto" es una buena respuesta
Kolob Canyon
44

Pero si quiero concatinar 2 cadenas, supongo que es mejor (más rápido) hacerlo sin StringBuilder. ¿Es esto correcto?

Eso es correcto, puede encontrar el por qué exactamente explicado muy bien en:

http://www.yoda.arachsys.com/csharp/stringbuilder.html

En resumen: si puedes concatinar cadenas de una sola vez, como

var result = a + " " + b  + " " + c + ..

está mejor sin StringBuilder porque solo se realiza una copia (la longitud de la cadena resultante se calcula de antemano);

Para estructuras como

var result = a;
result  += " ";
result  += b;
result  += " ";
result  += c;
..

se crean nuevos objetos cada vez, por lo que debería considerar StringBuilder.

Al final, el artículo resume estas reglas generales:

Reglas de juego

Entonces, ¿cuándo debería usar StringBuilder y cuándo debería usar los operadores de concatenación de cadenas?

  • Definitivamente use StringBuilder cuando esté concatenando en un bucle no trivial, especialmente si no sabe con certeza (en el momento de la compilación) cuántas iteraciones realizará a través del bucle. Por ejemplo, leer un archivo de un carácter a la vez, construir una cadena sobre la marcha usando el operador + = es potencialmente un suicidio de rendimiento.

  • Definitivamente use el operador de concatenación cuando pueda (de manera legible) especificar todo lo que debe concatenarse en una declaración. (Si tiene una variedad de cosas para concatenar, considere llamar a String.Concat explícitamente, o String.Join si necesita un delimitador).

  • No tenga miedo de dividir los literales en varios bits concatenados; el resultado será el mismo. Puede mejorar la legibilidad dividiendo un literal largo en varias líneas, por ejemplo, sin dañar el rendimiento.

  • Si necesita los resultados intermedios de la concatenación para algo más que alimentar la siguiente iteración de concatenación, StringBuilder no lo ayudará. Por ejemplo, si crea un nombre completo a partir de un nombre y un apellido, y luego agrega una tercera información (el apodo, tal vez) al final, solo se beneficiará de usar StringBuilder si no lo hace necesita la cadena (nombre + apellido) para otro propósito (como lo hacemos en el ejemplo que crea un objeto Persona).

  • Si solo tiene que hacer algunas concatenaciones y realmente quiere hacerlas en declaraciones separadas, no importa en qué dirección vaya. La forma más eficiente dependerá del número de concatenaciones, los tamaños de cadena involucrados y el orden en el que se concatenan. Si realmente cree que ese fragmento de código es un cuello de botella en el rendimiento, perfílelo o evalúelo en ambos sentidos.

Pedro
fuente
13

System.String es un objeto inmutable; significa que cada vez que modifiques su contenido, asignará una nueva cadena y esto lleva tiempo (¿y memoria?). Con StringBuilder, modifica el contenido real del objeto sin asignar uno nuevo.

Por lo tanto, use StringBuilder cuando necesite hacer muchas modificaciones en la cadena.

Víctor Hurdugaci
fuente
8

Realmente no ... deberías usar StringBuilder si concatenas cadenas grandes o tienes muchas concatenaciones, como en un bucle.

Poli
fuente
1
Eso está mal. Debe usar StringBuildersolo si el bucle o la concatenación es un problema de rendimiento para las especificaciones.
Alex Bagnolini
2
@Alex: ¿No es siempre así? ;) No, en serio, siempre he usado StringBuilder para la concatenación dentro de un bucle ... sin embargo, todos mis bucles tienen más de 1k iteraciones ... @Binary: Normalmente, eso debería compilarse string s = "abcd", al menos eso es lo último Escuché ... aunque, con variables sería, muy probablemente, Concat.
Bobby
1
El hecho es que casi SIEMPRE NO ES el caso. Siempre usé operadores de cadena a + "hello" + "somethingelse"y nunca tuve que preocuparme por eso. Si se convierte en un problema, usaré StringBuilder. Pero no me preocupé por ello en primer lugar y pasé menos tiempo escribiéndolo.
Alex Bagnolini
3
No hay absolutamente ningún beneficio de rendimiento con cadenas grandes, solo con muchas concatenaciones.
Konrad Rudolph
1
@Konrad: ¿Estás seguro de que no hay ningún beneficio de rendimiento? Cada vez que concatenas cadenas grandes, estás copiando una gran cantidad de datos; Cada vez que concatenas cadenas pequeñas, solo estás copiando una pequeña cantidad de datos.
LukeH
6
  • Si concatenas cadenas en un bucle, deberías considerar usar StringBuilder en lugar de String normal
  • En caso de que sea una concatenación única, es posible que no vea la diferencia en el tiempo de ejecución en absoluto

Aquí hay una aplicación de prueba simple para demostrar el punto:

class Program
{
    static void Main(string[] args)
    {
        const int testLength = 30000;
        var StartTime = DateTime.Now;

        //TEST 1 - String
        StartTime = DateTime.Now;
        String tString = "test string";
        for (int i = 0; i < testLength; i++)
        {
            tString += i.ToString();
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 2000 ms

        //TEST 2 - StringBuilder
        StartTime = DateTime.Now;
        StringBuilder tSB = new StringBuilder("test string");
        for (int i = 0; i < testLength; i++)
        {
            tSB.Append(i.ToString());
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 4 ms

        Console.ReadLine();
    }
}

Resultados:

  • 30.000 iteraciones

    • Cadena - 2000 ms
    • StringBuilder - 4 ms
  • 1000 iteraciones

    • Cadena - 2 ms
    • StringBuilder - 1 ms
  • 500 iteraciones

    • Cadena - 0 ms
    • StringBuilder - 0 ms
adrian.krzysztofek
fuente
5

Parafrasear

Entonces contarás hasta tres, ni más ni menos. Tres será el número que contarás, y el número del conteo será tres. No contarás cuatro, ni contarás dos, excepto que luego pasarás a tres. Una vez que se alcanza el número tres, siendo el tercer número, entonces lanza su Granada de Mano Santa de Antioquía

Generalmente uso el generador de cadenas para cualquier bloque de código que resulte en la concatenación de tres o más cadenas.

Russell Steen
fuente
Depende: Concetanation solo hace una copia: "Russell" + "" + Steen + ".", Solo hará una copia porque calcula la longitud de la cuerda de antemano. Solo cuando tenga que dividir su concatenación, debe comenzar a pensar en un constructor
Peter
4

No hay una respuesta definitiva, solo reglas generales. Mis propias reglas personales son algo como esto:

  • Si concatena en un bucle, utilice siempre a StringBuilder.
  • Si las cadenas son grandes, utilice siempre a StringBuilder.
  • Si el código de concatenación está ordenado y se puede leer en la pantalla, probablemente esté bien.
    Si no es así, use un StringBuilder.
LukeH
fuente
Sé que este es un tema antiguo, pero solo sé aprender y quería saber qué consideras una "cadena grande".
MatthewD
4

Pero si quiero concatenar 2 cadenas, supongo que es mejor y más rápido hacerlo sin StringBuilder. ¿Es esto correcto?

Si. Pero lo que es más importante, es mucho más legible usar una vainilla Stringen tales situaciones. Usarlo en un bucle, por otro lado, tiene sentido y también puede ser tan legible como la concatenación.

Sería cauteloso con las reglas generales que citan números específicos de concatenación como umbral. Usarlo en bucles (y solo en bucles) es probablemente igual de útil, más fácil de recordar y tiene más sentido.

Konrad Rudolph
fuente
"Sería cauteloso con las reglas generales que citan números específicos de concatenación como umbral" <this. Además, después de aplicar el sentido común, piense en la persona que regresará a su código en 6 meses.
Phil Cooper
3

Siempre que pueda escribir físicamente el número de concatenaciones (a + b + c ...) no debería haber una gran diferencia. N al cuadrado (en N = 10) es una desaceleración de 100 veces, lo que no debería ser tan malo.

El gran problema es cuando está concatenando cientos de cadenas. A N = 100, obtienes una ralentización de 10000 veces. Lo cual es bastante malo.

nostálgico
fuente
3

Dado que es difícil encontrar una explicación para esto que no esté influenciado por opiniones o seguido por una batalla de orgullo, pensé en escribir un poco de código en LINQpad para probarlo yo mismo.

Descubrí que usar cadenas de tamaño pequeño en lugar de usar i.ToString () cambia los tiempos de respuesta (visibles en pequeños bucles).

La prueba utiliza diferentes secuencias de iteraciones para mantener las mediciones de tiempo en rangos sensiblemente comparables.

Copiaré el código al final para que pueda probarlo usted mismo (results.Charts ... Dump () no funcionará fuera de LINQPad).

Salida (eje X: número de iteraciones probadas, eje Y: tiempo empleado en tics):

Secuencia de iteraciones: 2, 3, 4, 5, 6, 7, 8, 9, 10 Secuencia de iteraciones: 2, 3, 4, 5, 6, 7, 8, 9, 10

Secuencia de iteraciones: 10, 20, 30, 40, 50, 60, 70, 80 Secuencia de iteraciones: 10, 20, 30, 40, 50, 60, 70, 80

Secuencia de iteraciones: 100, 200, 300, 400, 500 Secuencia de iteraciones: 100, 200, 300, 400, 500

Código (escrito con LINQPad 5):

void Main()
{
    Test(2, 3, 4, 5, 6, 7, 8, 9, 10);
    Test(10, 20, 30, 40, 50, 60, 70, 80);
    Test(100, 200, 300, 400, 500);
}

void Test(params int[] iterationsCounts)
{
    $"Iterations sequence: {string.Join(", ", iterationsCounts)}".Dump();

    int testStringLength = 10;
    RandomStringGenerator.Setup(testStringLength);
    var sw = new System.Diagnostics.Stopwatch();
    var results = new Dictionary<int, TimeSpan[]>();

    // This call before starting to measure time removes initial overhead from first measurement
    RandomStringGenerator.GetRandomString(); 

    foreach (var iterationsCount in iterationsCounts)
    {
        TimeSpan elapsedForString, elapsedForSb;

        // string
        sw.Restart();
        var str = string.Empty;

        for (int i = 0; i < iterationsCount; i++)
        {
            str += RandomStringGenerator.GetRandomString();
        }

        sw.Stop();
        elapsedForString = sw.Elapsed;


        // string builder
        sw.Restart();
        var sb = new StringBuilder(string.Empty);

        for (int i = 0; i < iterationsCount; i++)
        {
            sb.Append(RandomStringGenerator.GetRandomString());
        }

        sw.Stop();
        elapsedForSb = sw.Elapsed;

        results.Add(iterationsCount, new TimeSpan[] { elapsedForString, elapsedForSb });
    }


    // Results
    results.Chart(r => r.Key)
    .AddYSeries(r => r.Value[0].Ticks, LINQPad.Util.SeriesType.Line, "String")
    .AddYSeries(r => r.Value[1].Ticks, LINQPad.Util.SeriesType.Line, "String Builder")
    .DumpInline();
}

static class RandomStringGenerator
{
    static Random r;
    static string[] strings;

    public static void Setup(int testStringLength)
    {
        r = new Random(DateTime.Now.Millisecond);

        strings = new string[10];
        for (int i = 0; i < strings.Length; i++)
        {
            strings[i] = Guid.NewGuid().ToString().Substring(0, testStringLength);
        }
    }

    public static string GetRandomString()
    {
        var indx = r.Next(0, strings.Length);
        return strings[indx];
    }
}
Menol
fuente
2

No creo que haya una línea muy fina entre cuándo usar y cuándo no. A menos que, por supuesto, alguien haya realizado pruebas exhaustivas para obtener las condiciones de oro.

Para mí, no usaré StringBuilder si solo concateno 2 cadenas enormes. Si hay un bucle con un recuento indeterminista, es probable que lo haga, incluso si el bucle puede ser un recuento pequeño.

OK W
fuente
De hecho, sería completamente incorrecto que ues StringBuilder concat 2 cadenas, pero eso no tiene nada que ver con perf. pruebas, eso es simplemente usarlo para algo incorrecto.
Marc Gravell
1

Una sola concatenación no vale la pena usar StringBuilder. Por lo general, he usado 5 concatenaciones como regla general.

Matt Wrock
fuente