Le moyen le plus efficace de concaténer des chaînes?

286

Quelle est la manière la plus efficace de concaténer des chaînes?

jimmij
la source
9
Je tiens ici à mettre en garde contre le fait que la réponse acceptée est considérablement incomplète car elle ne traite pas de tous les cas pertinents.
usr
@usr Indeed ... des informations plus détaillées sur StringBuilderles cas d'utilisation peuvent être trouvées ici .
Tamir Vered du
Mon nouveau favori depuis C # 6 est $ "Texte constant ici {foo} et {bar}" ... c'est comme String.Formatsur les stéroïdes. Ce qui, en termes de performances, est un tout petit peu plus lent sur une ligne que +et String.Concat, mais beaucoup mieux que ceux, bien que plus lent que StringBuildersur plusieurs appels. Pratiquement parlant, les différences de performances sont telles que, si je devais choisir une seule façon de concaténer, je choisirais les interpolations de chaînes en utilisant $... Si deux façons, puis ajouter StringBuilderà ma boîte à outils. Avec ces deux façons, vous êtes prêt.
u8it le
La String.Joinréponse ci-dessous ne rend pas +justice et est, en pratique, une mauvaise façon de concaténer des cordes, mais elle est étonnamment rapide en termes de performances. La réponse est intéressante. String.Concatet String.Joinpeut tous deux agir sur des tableaux, mais String.Joinest en fait plus rapide. Apparemment, String.Joinest assez sophistiqué et plus optimisé que String.Concat, en partie parce qu'il fonctionne de la même manière StringBuilderqu'il calcule d'abord la longueur de la chaîne, puis construit la chaîne bénéficiant de ces connaissances à l'aide de UnSafeCharBuffer.
u8it le
Ok, donc c'est rapide, mais cela String.Joinnécessite aussi de construire un tableau qui semble inefficace sur le plan des ressources, n'est-ce pas? ... Il s'avère que +et String.Concatconstruire des tableaux pour leurs constituants de toute façon. Par conséquent, la création manuelle d'un tableau et son alimentation String.Joinest relativement plus rapide ... cependant, StringBuildersurpasse toujours String.Joinde toutes les manières pratiques alors qu'il $n'est que légèrement plus lent et beaucoup plus rapide sur les longues chaînes ... sans oublier qu'il est maladroit et laid à utiliser String.Joinsi vous avez pour créer un tableau pour elle sur place.
u8it le

Réponses:

155

La StringBuilder.Append()méthode est bien meilleure que l'utilisation de l' +opérateur. Mais j'ai trouvé que, lors de l'exécution de 1000 concaténations ou moins, String.Join()c'est encore plus efficace que StringBuilder.

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

Le seul problème avec String.Joinest que vous devez concaténer les chaînes avec un délimiteur commun.

Edit: comme l' a souligné @ryanversaw , vous pouvez faire le délimiteur string.Empty.

string key = String.Join("_", new String[] 
{ "Customers_Contacts", customerID, database, SessionID });
TheEmirOfGroofunkistan
la source
11
StringBuildera un énorme coût de démarrage comparable, il n'est efficace que lorsqu'il est utilisé avec de très grandes chaînes ou de très nombreuses concaténations. Il n'est pas trivial de découvrir une situation donnée. Si les performances sont problématiques, le profilage est votre ami (voir ANTS).
Abel
32
Ce n'est pas le cas pour la concaténation d'une seule ligne. Disons que vous faites myString = "foo" + var1 + "bar" + var2 + "hello" + var3 + "world", le compilateur transforme automatiquement cela en un appel string.concat, qui est aussi efficace que possible. Cette réponse est incorrecte, il y a beaucoup de meilleures réponses parmi
lesquelles
2
Pour une concatentation de chaînes triviale, utilisez ce qui est le plus lisible. chaîne a = b + c + d; sera presque toujours plus rapide que de le faire avec StringBuilder, mais la différence n'est généralement pas pertinente. Utilisez StringBuilder (ou une autre option de votre choix) lors de l'ajout répété à la même chaîne (par exemple, la création d'un rapport) ou lorsque vous traitez avec de grandes chaînes.
Swanny
5
Pourquoi n'en avez-vous pas parlé string.Concat?
Venemo
272

Rico Mariani , le gourou de la performance .NET, avait un article sur ce même sujet. Ce n'est pas aussi simple qu'on pourrait le penser. Le conseil de base est le suivant:

Si votre motif ressemble à:

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

c'est un concat et c'est zippy, StringBuilder n'aidera probablement pas.

Si votre motif ressemble à:

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

alors vous voulez probablement StringBuilder.

Un autre article à l'appui de cette affirmation vient d'Eric Lippert où il décrit de +manière détaillée les optimisations effectuées sur des concaténations sur une ligne .

Lee
la source
1
Qu'en est-il de String.Format ()?
IronSlug
86

Il existe 6 types de concaténations de chaînes:

  1. Utilisation du +symbole plus ( ).
  2. Utilisation string.Concat().
  3. Utilisation string.Join().
  4. Utilisation string.Format().
  5. Utilisation string.Append().
  6. Utilisation StringBuilder.

Dans une expérience, il a été prouvé que string.Concat()c'est la meilleure façon d'approcher si les mots sont inférieurs à 1000 (approximativement) et si les mots sont supérieurs à 1000 alors StringBuilderdoivent être utilisés.

Pour plus d'informations, consultez ce site .

string.Join () vs string.Concat ()

La méthode string.Concat ici est équivalente à l'invocation de la méthode string.Join avec un séparateur vide. Ajouter une chaîne vide est rapide, mais ne pas le faire est encore plus rapide, donc la méthode string.Concat serait supérieure ici.

Monsieur green
la source
4
En cas de lecture, il a été prouvé que string.Concat () ou + est le meilleur moyen. Oui, je peux l'obtenir de l'article, mais cela me fait gagner un clic. Ainsi, + et concat compilent dans le même code.
brumScouse
J'ai utilisé cette base pour essayer de rendre une de mes méthodes plus efficace, où je n'avais besoin que de concaténer exactement 3 chaînes. J'ai trouvé que +c'était en fait 3 millisecondes plus rapide que string.Concat(), même si je n'ai pas examiné la quantité de chaînes nécessaires avant les string.Concat()dépassements +.
Gnemlock
59

Depuis Chinh Do - StringBuilder n'est pas toujours plus rapide :

Règles de base

  • Lors de la concaténation de trois valeurs de chaîne dynamique ou moins, utilisez la concaténation de chaîne traditionnelle.

  • Lors de la concaténation de plus de trois valeurs de chaîne dynamique, utilisez StringBuilder.

  • Lorsque vous créez une grande chaîne à partir de plusieurs littéraux de chaîne, utilisez soit le @littéral de chaîne, soit l'opérateur inline +.

La plupart du temps StringBuilderest votre meilleur pari, mais il y a des cas, comme indiqué dans ce post, que vous devriez au moins penser à chaque situation.

cheval pâle
la source
8
afaik @ désactive uniquement le traitement des séquences d'échappement. msdn.microsoft.com/en-us/library/362314fe.aspx d' accord
abatishchev
12

Si vous travaillez en boucle, StringBuilderc'est probablement la voie à suivre; cela vous évite d'avoir à créer régulièrement de nouvelles chaînes. Dans le code, cela ne fonctionnera qu'une seule fois, String.Concatc'est probablement bien.

Cependant, Rico Mariani (gourou de l'optimisation .NET) a composé un quiz dans lequel il a déclaré à la fin que, dans la plupart des cas, il recommande String.Format.

Adam V
la source
Cela fait des années que je recommande l'utilisation de string.format sur string + string aux gens avec qui j'ai travaillé. Je pense que les avantages de lisibilité sont un avantage supplémentaire au-delà de l'avantage de performance.
Scott Lawrence
1
Ceci est la vraie réponse correcte. La réponse actuellement acceptée pour StringBuilder est incorrecte, car elle ne mentionne pas les ajouts de ligne unique pour lesquels string.concat ou + est plus rapide. Le fait peu connu est que le compilateur traduit réellement les + en string.concat. En outre, pour les boucles ou pour les concatients de plusieurs lignes, j'utilise un générateur de chaînes personnalisé qui ne s'ajoute que lorsque .ToString est appelé - surmonter le problème de tampon indéterminé que StringBuilder a
csauve
2
string.Format n'est en aucun cas le moyen le plus rapide. Je ne sais pas comment concevoir un cas où cela se produit.
usr
@usr - notez que Rico ne dit pas explicitement que c'est le plus rapide , mais simplement que c'est sa recommandation: "Même si c'est le moins performant, et nous le savions bien à l'avance, vos deux architectes de performance CLR conviennent que [string.Format] devrait être le choix par défaut. Dans le cas très improbable où cela deviendrait un problème de performance, le problème est facilement résolu avec seulement de modestes modifications locales. Normalement, vous ne faites que profiter d'une bonne maintenabilité. "
Adam V
@AdamV, la question concerne le moyen le plus rapide. Je ne suis pas d'accord pour que ce soit le choix par défaut, mais pas pour des raisons de performance. Cela peut être une syntaxe maladroite. Resharper peut effectuer des conversions avant et en arrière à volonté.
usr
10

Voici la méthode la plus rapide que j'ai développée au cours d'une décennie pour mon application NLP à grande échelle. J'ai des variations pour IEnumerable<T>et d'autres types d'entrée, avec et sans séparateurs de différents types ( Char, String), mais ici je montre le cas simple de la concaténation de toutes les chaînes d'un tableau en une seule chaîne, sans séparateur. La dernière version ici est développée et testée à l'unité sur C # 7 et .NET 4.7 .

Il existe deux clés pour des performances plus élevées; la première consiste à pré-calculer la taille totale exacte requise. Cette étape est triviale lorsque l'entrée est un tableau comme illustré ici. Pour la gestion à la IEnumerable<T>place, il convient de rassembler d'abord les chaînes dans un tableau temporaire pour calculer ce total (le tableau est nécessaire pour éviter d'appeler ToString()plus d'une fois par élément car techniquement, étant donné la possibilité d'effets secondaires, cela pourrait changer la sémantique attendue d'une opération de "jointure de chaîne").

Ensuite, étant donné la taille totale de l'allocation de la chaîne finale, la plus grande amélioration des performances est obtenue en créant la chaîne de résultat en place . Cela nécessite la technique (peut-être controversée) de suspendre temporairement l'immuabilité d'un nouveau Stringqui est initialement alloué plein de zéros. Une telle controverse mise à part, cependant ...

... notez qu'il s'agit de la seule solution de concaténation en masse sur cette page qui évite entièrement un tour supplémentaire d'allocation et de copie par le Stringconstructeur.

Code complet:

/// <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);

Je dois mentionner que ce code a une légère modification de ce que j'utilise moi-même. Dans l'original, j'appelle l' instruction cpblk IL à partir de C # pour effectuer la copie réelle. Pour plus de simplicité et de portabilité dans le code ici, j'ai remplacé cela par P / Invoke à la memcpyplace, comme vous pouvez le voir. Pour des performances optimales sur x64 ( mais peut-être pas x86 ), vous souhaiterez peut-être utiliser la méthode cpblk à la place.

Glenn Slayden
la source
string.Joinfait déjà toutes ces choses pour vous. Il n'est pas nécessaire de l'écrire vous-même. Il calcule la taille de la chaîne finale, construit une chaîne de cette taille, puis écrit dans le tableau de caractères sous-jacent. Il a même l'avantage d'utiliser des noms de variables lisibles dans le processus.
Servy
1
@Servy Merci pour le commentaire; String.Joinpeut en effet être efficace. Comme je l'ai laissé entendre dans l'intro, le code ici n'est que l'illustration la plus simple d'une famille de fonctions que j'utilise pour des scénarios qui String.Joinne gèrent pas (comme l'optimisation pour le Charséparateur) ou ne le géraient pas dans les versions précédentes de .NET. Je suppose que je n'aurais pas dû choisir cela pour l'exemple le plus simple, car c'est un cas qui String.Joingère déjà bien, bien qu'avec «l'inefficacité», probablement incommensurable, du traitement d'un séparateur vide, à savoir. String.Empty.
Glenn Slayden
Bien sûr, dans le cas où vous n'avez pas de séparateur, vous devez appeler Concat, ce qui le fait également correctement. De toute façon, vous n'avez pas besoin d'écrire le code vous-même.
Servy
7
@Servy J'ai comparé les performances de String.Joinmon code à l'aide de ce harnais de test . Pour 10 millions d'opérations de concaténation aléatoires contenant jusqu'à 100 chaînes de taille de mot chacune, le code ci-dessus est systématiquement 34% plus rapide que String.Joinsur la version x64 avec .NET 4.7 . Puisque l'OP demande explicitement la méthode "la plus efficace", le résultat suggère que ma réponse s'applique. Si cela répond à vos préoccupations, je vous invite à reconsidérer votre downvote.
Glenn Slayden
1
J'ai récemment étalonnées sur ce x64 CLR complet 4.7.1 et trouve qu'il est environ deux fois plus vite que string.join avec environ 25% moins de mémoire allouée ( i.imgur.com/SxIpEmL.png ) lors de l' utilisation cpblk ou github.com/ JonHanna / Mnemosyne
quentin-starin
6

De cet article MSDN :

Il y a une surcharge associée à la création d'un objet StringBuilder, à la fois en temps et en mémoire. Sur une machine à mémoire rapide, un StringBuilder vaut la peine si vous effectuez environ cinq opérations. En règle générale, je dirais que 10 opérations de chaîne ou plus sont une justification de la surcharge sur n'importe quelle machine, même plus lente.

Donc, si vous faites confiance à MSDN, utilisez StringBuilder si vous devez effectuer plus de 10 opérations / concaténations de chaînes - sinon, une simple concaténation de chaînes avec «+» est très bien.

JohnIdol
la source
5

En ajoutant aux autres réponses, veuillez garder à l'esprit que StringBuilder peut recevoir une quantité initiale de mémoire à allouer .

Le paramètre de capacité définit le nombre maximal de caractères pouvant être stockés dans la mémoire allouée par l'instance actuelle. Sa valeur est affectée à la propriété Capacity . Si le nombre de caractères à stocker dans l'instance actuelle dépasse cette valeur de capacité , l'objet StringBuilder alloue de la mémoire supplémentaire pour les stocker.

Si la capacité est nulle, la capacité par défaut spécifique à l'implémentation est utilisée.

L'ajout répété à un StringBuilder qui n'a pas été pré-alloué peut entraîner de nombreuses allocations inutiles, tout comme la concaténation répétée de chaînes régulières.

Si vous savez combien de temps durera la chaîne finale, pouvez la calculer trivialement ou faire une supposition éclairée sur le cas commun (allouer trop n'est pas nécessairement une mauvaise chose), vous devez fournir ces informations au constructeur ou au Propriété de capacité . Surtout lors de l'exécution de tests de performances pour comparer StringBuilder avec d'autres méthodes comme String.Concat, qui font la même chose en interne. Tout test que vous voyez en ligne qui n'inclut pas la pré-allocation de StringBuilder dans ses comparaisons est faux.

Si vous ne pouvez pas deviner la taille, vous écrivez probablement une fonction utilitaire qui devrait avoir son propre argument facultatif pour contrôler la pré-allocation.

DBN
la source
4

Voici peut-être une autre solution alternative pour concaténer plusieurs chaînes.

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

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

interpolation de chaîne

RP Nainwal
la source
1
Ceci est en fait surprenant comme méthode générale de concaténation. C'est fondamentalement String.Formatmais plus lisible et plus facile à utiliser. Banc-marquage, il est légèrement plus lent que +et String.Concatà une ligne de concaténations , mais beaucoup mieux que les deux personnes à des appels répétitifs rendant StringBuildermoins nécessaire.
u8it le
2

Le plus efficace est d'utiliser StringBuilder, comme ceci:

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

@jonezy: String.Concat est très bien si vous avez quelques petites choses. Mais si vous concaténez des mégaoctets de données, votre programme sera probablement chargé.

Le Schtroumpf
la source
hé quelle est la solution pour les mégaoctets de données?
Neel
2

Essayez ces 2 morceaux de code et vous trouverez la solution.

 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();
    }

Contre

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

Vous constaterez que le 1er code se terminera très rapidement et la mémoire sera en bonne quantité.

Le deuxième code peut-être que la mémoire sera correcte, mais cela prendra plus de temps ... beaucoup plus de temps. Donc, si vous avez une application pour beaucoup d'utilisateurs et que vous avez besoin de vitesse, utilisez la 1ère. Si vous avez une application pour une application utilisateur à court terme, vous pouvez peut-être utiliser les deux ou la seconde sera plus "naturelle" pour les développeurs.

À votre santé.

Eduardo Mass
la source
1

Pour seulement deux chaînes, vous ne voulez certainement pas utiliser StringBuilder. Il existe un certain seuil au-dessus duquel la surcharge de StringBuilder est inférieure à la surcharge d'allocation de plusieurs chaînes.

Donc, pour plus de 2-3 chaînes, utilisez le code de DannySmurf . Sinon, utilisez simplement l'opérateur +.

pseudo
la source
1

System.String est immuable. Lorsque nous modifions la valeur d'une variable de chaîne, une nouvelle mémoire est allouée à la nouvelle valeur et l'allocation de mémoire précédente libérée. System.StringBuilder a été conçu pour avoir le concept d'une chaîne mutable où une variété d'opérations peuvent être effectuées sans attribuer un emplacement de mémoire séparé pour la chaîne modifiée.

Dhibi_Mohanned
la source
1

Une autre solution:

à l'intérieur de la boucle, utilisez List au lieu de string.

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

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

c'est très très rapide.

asady
la source
0

Cela dépendrait du code. StringBuilder est généralement plus efficace, mais si vous concaténez seulement quelques chaînes et faites tout cela sur une seule ligne, les optimisations de code s'en occuperont probablement pour vous. Il est important de penser à quoi ressemble le code: pour les ensembles plus grands, StringBuilder facilitera la lecture, pour les petits, StringBuilder ajoutera simplement un encombrement inutile.

Jon Dewees
la source