¿La forma más eficiente de concatenar cadenas?

285

¿Cuál es la forma más eficiente de concatenar cadenas?

jimmij
fuente
9
Quiero colocar una advertencia destacada aquí de que la respuesta aceptada es significativamente incompleta porque no discute todos los casos relevantes.
Usr
@usr Indeed ... StringBuilderpuede encontrar información más detallada sobre casos de uso aquí .
Tamir Vered
Mi nuevo favorito a partir de C # 6 es $ "Texto constante aquí {foo} y {bar}" ... es como String.Formaten los esteroides. Lo cual, en cuanto al rendimiento, es un poco más lento en una línea +y String.Concat, pero mucho mejor que eso, aunque más lento que StringBuilderen varias llamadas. Hablando en términos prácticos, las diferencias de rendimiento son tales que, si tuviera que elegir solo una forma de concatenar, elegiría interpolaciones de cadenas usando $... Si hay dos formas, luego agregue StringBuildera mi caja de herramientas. Con esas dos formas estás listo.
u8it
La String.Joinrespuesta a continuación no hace +justicia y es, prácticamente hablando, una mala manera de concatenar cadenas, pero es sorprendentemente rápido en cuanto a rendimiento. La respuesta por qué es interesante. String.Concaty String.Joinambos pueden actuar en matrices, pero en String.Joinrealidad es más rápido. Aparentemente, String.Joines bastante sofisticado y más optimizado que String.Concat, en parte porque funciona de manera similar, ya StringBuilderque calcula la longitud de la cadena primero y luego construye la cadena que se beneficia de este conocimiento utilizando UnSafeCharBuffer.
u8it
Ok, entonces es rápido, pero String.Jointambién requiere construir una matriz que parezca ineficiente para los recursos, ¿verdad? ... Resulta eso +y String.Concatconstruye matrices para sus componentes de todos modos. En consecuencia, la creación manual de una matriz y su alimentación String.Joines comparativamente más rápida ... sin embargo, StringBuilderaún tiene un rendimiento superior String.Joinen casi todas las formas prácticas, mientras que $es solo un poco más lenta y mucho más rápida en cadenas largas ... sin mencionar que es incómodo y feo de usar String.Joinsi tiene para crear una matriz para ello en el acto.
u8it

Respuestas:

154

El StringBuilder.Append()método es mucho mejor que usar el +operador. Pero he descubierto que, al ejecutar 1000 concatenaciones o menos, String.Join()es aún más eficiente que StringBuilder.

StringBuilder sb = new StringBuilder();
sb.Append(someString);

El único problema con String.Joines que tienes que concatenar las cadenas con un delimitador común.

Editar: como señaló @ryanversaw , puede hacer el delimitador string.Empty.

string key = String.Join("_", new String[] 
{ "Customers_Contacts", customerID, database, SessionID });
TheEmirOfGroofunkistan
fuente
11
StringBuildertiene un enorme costo de inicio comparable, solo es eficiente cuando se usa con cadenas muy grandes o con muchas concatenaciones. No es trivial descubrir una situación determinada. Si el rendimiento es problemático, la creación de perfiles es su amigo (consulte ANTS).
Abel el
32
Esto no es cierto para la concatenación de una sola línea. Digamos que haces myString = "foo" + var1 + "bar" + var2 + "hello" + var3 + "world", el compilador lo convierte automáticamente en una llamada string.concat, que es lo más eficiente posible. Esta respuesta es incorrecta, hay muchas mejores respuestas para elegir
csauve
2
Para la concatenación de cadenas triviales, use lo que sea más legible. cadena a = b + c + d; casi siempre será más rápido que hacerlo con StringBuilder, pero la diferencia suele ser irrelevante. Utilice StringBuilder (u otra opción de su elección) al agregar repetidamente a la misma cadena (por ejemplo, crear un informe) o al tratar con cadenas grandes.
Swanny
55
¿Por qué no lo has mencionado string.Concat?
Venemo
271

Rico Mariani , el gurú de .NET Performance, tenía un artículo sobre este mismo tema. No es tan simple como uno podría sospechar. El consejo básico es este:

Si su patrón se ve así:

x = f1(...) + f2(...) + f3(...) + f4(...)

ese es un concat y es rápido, StringBuilder probablemente no ayudará.

Si su patrón se ve así:

if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)

entonces probablemente quieras StringBuilder.

Otro artículo para respaldar esta afirmación proviene de Eric Lippert, donde describe las optimizaciones realizadas en +concatenaciones de una línea de manera detallada.

Sotavento
fuente
1
¿Qué pasa con String.Format ()?
IronSlug
85

Hay 6 tipos de concatenaciones de cadenas:

  1. Usando el +símbolo más ( ).
  2. Utilizando string.Concat().
  3. Utilizando string.Join().
  4. Utilizando string.Format().
  5. Utilizando string.Append().
  6. Utilizando StringBuilder.

En un experimento, se ha demostrado que string.Concat()es la mejor manera de acercarse si las palabras son menos de 1000 (aproximadamente) y si las palabras son más de 1000, entonces StringBuilderdeben usarse.

Para obtener más información, consulte este sitio .

string.Join () vs string.Concat ()

El método string.Concat aquí es equivalente a la invocación del método string.Join con un separador vacío. Agregar una cadena vacía es rápido, pero no hacerlo es aún más rápido, por lo que el método string.Concat sería superior aquí.

SR verde
fuente
44
Debería leerse, se ha probado string.Concat () o + es la mejor manera. Sí, puedo obtener esto del artículo, pero me ahorra un clic. Entonces, + y concat se compilan en el mismo código.
brumScouse
Utilicé esta base para tratar de hacer un método mío más eficiente, donde solo necesitaba concatenar exactamente 3 cadenas. Descubrí que en +realidad era 3 milisegundos más rápido que string.Concat(), aunque no he examinado la cantidad de cadenas requeridas antes de los string.Concat()atropellos +.
Gnemlock
59

De Chinh Do: StringBuilder no siempre es más rápido :

Reglas de juego

  • Cuando concatene tres valores de cadena dinámica o menos, use la concatenación de cadena tradicional.

  • Cuando concatene más de tres valores de cadena dinámica, use StringBuilder.

  • Cuando construya una cadena grande a partir de varios literales de cadena, use el @literal de cadena o el operador en línea +.

La mayoría de las veces StringBuilderes su mejor apuesta, pero hay casos, como se muestra en esa publicación, que al menos debería pensar en cada situación.

caballo pálido
fuente
8
afaik @ solo desactiva el procesamiento de secuencias de escape. msdn.microsoft.com/en-us/library/362314fe.aspx de acuerdo
abatishchev
12

Si está operando en un bucle, StringBuilderes probablemente el camino a seguir; le ahorra la sobrecarga de crear nuevas cadenas regularmente. Sin embargo, en el código que solo se ejecutará una vez, String.Concatprobablemente esté bien.

Sin embargo, Rico Mariani (gurú de la optimización .NET) realizó un cuestionario en el que afirmó al final que, en la mayoría de los casos, recomienda String.Format.

Adam V
fuente
He recomendado el uso de string.format sobre string + string durante años a las personas con las que he trabajado. Creo que las ventajas de legibilidad son una ventaja adicional más allá del beneficio de rendimiento.
Scott Lawrence
1
Esta es la respuesta correcta real. La respuesta actualmente aceptada para StringBuilder es incorrecta, ya que no menciona una sola línea para la cual string.concat o + es más rápido. Un hecho poco conocido es que el compilador en realidad traduce + 's en string.concat's. Además, los bucles o para múltiples concats línea utilizo un constructor de cadena hecha a la medida que sólo anexa al .ToString se llama - la superación del problema búfer de indeterminados que tiene StringBuilder
csauve
2
string.Format no es la forma más rápida bajo ninguna circunstancia. No sé cómo idear un caso donde se presenta.
Usr
@usr - tenga en cuenta que Rico no dice explícitamente que es el más rápido , solo que es su recomendación: "Aunque es el peor desempeño, y lo sabíamos de antemano, sus dos arquitectos de rendimiento CLR coinciden en que [string.Format] debería ser la opción predeterminada. En el caso altamente improbable de que se convierta en un problema de rendimiento, el problema se puede solucionar fácilmente con solo cambios locales modestos. Normalmente, solo está sacando provecho de una buena capacidad de mantenimiento ".
Adam V
@AdamV la pregunta es sobre la forma más rápida. Estoy de acuerdo en que es la opción predeterminada, aunque no por razones de rendimiento. Puede ser una sintaxis torpe. Resharper puede convertir de ida y vuelta a voluntad.
Usr
10

Este es el método más rápido que he desarrollado durante una década para mi aplicación NLP a gran escala. Tengo variaciones para IEnumerable<T>y otros tipos de entrada, con y sin separadores de diferentes tipos ( Char, String), pero aquí muestro el caso simple de concatenar todas las cadenas en una matriz en una sola cadena, sin separador. La última versión aquí está desarrollada y probada en C # 7 y .NET 4.7 .

Hay dos claves para un mayor rendimiento; el primero es calcular previamente el tamaño total exacto requerido. Este paso es trivial cuando la entrada es una matriz como se muestra aquí. En IEnumerable<T>cambio, para el manejo , vale la pena reunir primero las cadenas en una matriz temporal para calcular ese total (la matriz se requiere para evitar llamar ToString()más de una vez por elemento, ya que técnicamente, dada la posibilidad de efectos secundarios, hacerlo podría cambiar la semántica esperada de una operación de 'unión de cadenas').

A continuación, dado el tamaño total de asignación de la cadena final, el mayor impulso en el rendimiento se obtiene al construir la cadena de resultados en el lugar . Hacer esto requiere la técnica (quizás controvertida) de suspender temporalmente la inmutabilidad de un nuevo Stringque inicialmente se asigna lleno de ceros. Dejando a un lado cualquier controversia, sin embargo ...

... tenga en cuenta que esta es la única solución de concatenación masiva en esta página que evita por completo una ronda adicional de asignación y copia por parte del Stringconstructor.

Código completo:

/// <summary>
/// Concatenate the strings in 'rg', none of which may be null, into a single String.
/// </summary>
public static unsafe String StringJoin(this String[] rg)
{
    int i;
    if (rg == null || (i = rg.Length) == 0)
        return String.Empty;

    if (i == 1)
        return rg[0];

    String s, t;
    int cch = 0;
    do
        cch += rg[--i].Length;
    while (i > 0);
    if (cch == 0)
        return String.Empty;

    i = rg.Length;
    fixed (Char* _p = (s = new String(default(Char), cch)))
    {
        Char* pDst = _p + cch;
        do
            if ((t = rg[--i]).Length > 0)
                fixed (Char* pSrc = t)
                    memcpy(pDst -= t.Length, pSrc, (UIntPtr)(t.Length << 1));
        while (pDst > _p);
    }
    return s;
}

[DllImport("MSVCR120_CLR0400", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe void* memcpy(void* dest, void* src, UIntPtr cb);

Debo mencionar que este código tiene una ligera modificación de lo que yo uso. En el original, llamo a la instrucción cpblk IL de C # para hacer la copia real. Por simplicidad y portabilidad en el código aquí, reemplacé eso con P / Invoke memcpy, como puede ver. Para obtener el máximo rendimiento en x64 ( pero quizás no en x86 ), puede utilizar el método cpblk .

Glenn Slayden
fuente
string.Joinhace todas estas cosas ya por ti. No hay necesidad de escribirlo usted mismo. Calcula el tamaño de la cadena final, construye una cadena de ese tamaño y luego escribe en la matriz de caracteres subyacente. Incluso tiene la ventaja de usar nombres de variables legibles en el proceso.
Servy
1
@ Servy Gracias por el comentario; de hecho String.Joinpuede ser eficiente. Como indiqué en la introducción, el código aquí es solo la ilustración más simple de una familia de funciones que uso para escenarios que String.Joinno maneja (como la optimización para el Charseparador) o no manejó en versiones anteriores de .NET. Supongo que no debería haber escogido esto para el ejemplo más simple, ya que es un caso que String.Joinya se maneja bien, aunque con la "ineficiencia", probablemente inconmensurable, de procesar un separador vacío, a saber. String.Empty.
Glenn Slayden
Claro, en el caso de que no tenga un separador, debe llamar Concat, que también lo hace correctamente. De cualquier manera, no necesita escribir el código usted mismo.
Servy
77
@Servy He comparado el rendimiento de String.Joinmi código con este arnés de prueba . Para 10 millones de operaciones de concatenación aleatoria de hasta 100 cadenas de tamaño de palabra cada una, el código que se muestra arriba es consistentemente un 34% más rápido que String.Joinen la versión de lanzamiento x64 con .NET 4.7 . Dado que el OP solicita explícitamente el método "más eficiente", el resultado sugiere que se aplica mi respuesta. Si esto responde a sus inquietudes, lo invito a que reconsidere su voto negativo.
Glenn Slayden
1
Recientemente comparé esto en x64 full CLR 4.7.1 y descubrí que es aproximadamente el doble de rápido que la cadena. Únase con aproximadamente un 25% menos de memoria asignada ( i.imgur.com/SxIpEmL.png ) cuando use cpblk o github.com/ JonHanna / Mnemosyne
quentin-starin
6

De este artículo de MSDN :

Hay algunos gastos generales asociados con la creación de un objeto StringBuilder, tanto en tiempo como en memoria. En una máquina con memoria rápida, un StringBuilder vale la pena si está realizando unas cinco operaciones. Como regla general, diría que 10 o más operaciones de cadena es una justificación para la sobrecarga en cualquier máquina, incluso una más lenta.

Entonces, si confía en MSDN, vaya con StringBuilder si tiene que hacer más de 10 operaciones / concatenaciones de cadenas; de lo contrario, la cadena simple concat con '+' está bien.

JohnIdol
fuente
5

Además de las otras respuestas, tenga en cuenta que StringBuilder puede recibir una cantidad inicial de memoria para asignar .

El parámetro de capacidad define el número máximo de caracteres que se pueden almacenar en la memoria asignada por la instancia actual. Su valor se asigna a la propiedad Capacidad . Si el número de caracteres que se almacenarán en la instancia actual excede este valor de capacidad , el objeto StringBuilder asigna memoria adicional para almacenarlos.

Si la capacidad es cero, se utiliza la capacidad predeterminada específica de la implementación.

Agregar repetidamente a un StringBuilder que no ha sido preasignado puede resultar en muchas asignaciones innecesarias al igual que concatenar repetidamente cadenas regulares.

Si sabe cuánto durará la cadena final, puede calcularla trivialmente o puede hacer una suposición educada sobre el caso común (asignar demasiado no es necesariamente algo malo), debe proporcionar esta información al constructor o al Capacidad de propiedad. Especialmente cuando se ejecutan pruebas de rendimiento para comparar StringBuilder con otros métodos como String.Concat, que hacen lo mismo internamente. Cualquier prueba que vea en línea que no incluya la asignación previa de StringBuilder en sus comparaciones es incorrecta.

Si no puede adivinar el tamaño, probablemente esté escribiendo una función de utilidad que debería tener su propio argumento opcional para controlar la preasignación.

DBN
fuente
4

Lo siguiente puede ser una solución alternativa más para concatenar múltiples cadenas.

String str1 = "sometext";
string str2 = "some other text";

string afterConcate = $"{str1}{str2}";

interpolación de cuerdas

RP Nainwal
fuente
1
Esto es realmente sorprendente como un método general de concatenación. Básicamente es String.Formatmás legible y más fácil de trabajar. Benchmarking él, es ligeramente más lento que +y String.Concaten uno concatenaciones de línea, pero mucho mejor que ambos los que están en las llamadas repetitivas que hacen StringBuildermenos necesaria.
u8it
2

Lo más eficiente es usar StringBuilder, así:

StringBuilder sb = new StringBuilder();
sb.Append("string1");
sb.Append("string2");
...etc...
String strResult = sb.ToString();

@jonezy: String.Concat está bien si tienes un par de cosas pequeñas. Pero si está concatenando megabytes de datos, es probable que su programa se acumule.

El Pitufo
fuente
¿Cuál es la solución para megabytes de datos?
Neel
2

Pruebe estas 2 piezas de código y encontrará la solución.

 static void Main(string[] args)
    {
        StringBuilder s = new StringBuilder();
        for (int i = 0; i < 10000000; i++)
        {
            s.Append( i.ToString());
        }
        Console.Write("End");
        Console.Read();
    }

Vs

static void Main(string[] args)
    {
        string s = "";
        for (int i = 0; i < 10000000; i++)
        {
            s += i.ToString();
        }
        Console.Write("End");
        Console.Read();
    }

Encontrará que el primer código terminará realmente rápido y la memoria estará en una buena cantidad.

El segundo código tal vez la memoria estará bien, pero llevará más tiempo ... mucho más. Entonces, si tiene una aplicación para muchos usuarios y necesita velocidad, use la 1ra. Si tiene una aplicación para un usuario a corto plazo, tal vez pueda usar ambas o la segunda será más "natural" para los desarrolladores.

Salud.

Eduardo Mass
fuente
1

Para solo dos cadenas, definitivamente no desea usar StringBuilder. Hay un umbral por encima del cual la sobrecarga de StringBuilder es menor que la sobrecarga de asignar varias cadenas.

Entonces, para más de 2-3 cadenas, use el código de DannySmurf . De lo contrario, solo use el operador +.

Mella
fuente
1

System.String es inmutable. Cuando modificamos el valor de una variable de cadena, se asigna una nueva memoria al nuevo valor y se libera la asignación de memoria anterior. System.StringBuilder fue diseñado para tener el concepto de una cadena mutable donde se pueden realizar una variedad de operaciones sin asignación de ubicación de memoria separada para la cadena modificada.

Dhibi_Mohanned
fuente
1

Otra solución:

dentro del bucle, use List en lugar de string.

List<string> lst= new List<string>();

for(int i=0; i<100000; i++){
    ...........
    lst.Add(...);
}
return String.Join("", lst.ToArray());;

Es muy muy rápido.

tranquilo
fuente
0

Dependería del código. StringBuilder es más eficiente en general, pero si solo concatena unas pocas cadenas y lo hace todo en una línea, las optimizaciones de código probablemente se encargarán de usted. También es importante pensar en cómo se ve el código: para conjuntos más grandes StringBuilder facilitará la lectura, para los pequeños StringBuilder simplemente agregará desorden innecesario.

Jon Dewees
fuente