String vs StringBuilder


215

Je comprends la différence entre Stringet StringBuilder( StringBuilderétant mutable) mais y a-t-il une grande différence de performances entre les deux?

Le programme sur lequel je travaille a beaucoup d'annexes de chaînes pilotées par la casse (500+). Utilise-t-il StringBuilderun meilleur choix?

Réponses:


236

Oui, la différence de performances est significative. Consultez l'article de la base de connaissances « Comment améliorer les performances de concaténation de chaînes dans Visual C # ».

J'ai toujours essayé de coder pour plus de clarté, puis d'optimiser les performances plus tard. C'est beaucoup plus facile que de faire l'inverse! Cependant, après avoir vu l'énorme différence de performances dans mes applications entre les deux, j'y réfléchis maintenant un peu plus attentivement.

Heureusement, il est relativement simple d'exécuter une analyse des performances sur votre code pour voir où vous passez du temps, puis de le modifier pour l'utiliser StringBuildersi nécessaire.


44
Une bonne règle d'or consiste à utiliser des chaînes lorsque vous n'allez pas le modifier et à utiliser StringBuilder si vous souhaitez le modifier.
Tim

31
J'aime beaucoup cette réponse, en particulier les conseils de codage pour plus de clarté avant les performances. En tant que développeurs, nous passons autant ou plus de temps à lire du code qu'à l'écrire.
Scott Lawrence

Outlaw: Je pense que cela devrait devenir sa propre réponse qui est votée séparément, si je comprends bien StackOverflow.
Jay Bazuzi

2
sur la base de mon expérience, si vous essayez de concaténer environ 10-15 cordes, vous pouvez aller avec de la chaîne, mais si non. des chaînes sont plus que cela alors utilisez le générateur de chaînes
Peeyush

3
Cela dépend toujours de la façon dont on l'utilise, pour une référence aux tests que j'aime vraiment Coding Horror - The Sad Tragedy of Micro-Optimization Theatre
Erik Philips

56

Pour clarifier ce que Gillian a dit à propos de 4 cordes, si vous avez quelque chose comme ça:

string a,b,c,d;
 a = b + c + d;

alors ce serait plus rapide en utilisant des chaînes et l'opérateur plus. En effet (comme Java, comme le souligne Eric), il utilise automatiquement StringBuilder en interne (en fait, il utilise une primitive que StringBuilder utilise également)

Cependant, si ce que vous faites est plus proche de:

string a,b,c,d;
 a = a + b;
 a = a + c;
 a = a + d;

Ensuite, vous devez utiliser explicitement un StringBuilder. .Net ne crée pas automatiquement un StringBuilder ici, car ce serait inutile. À la fin de chaque ligne, "a" doit être une chaîne (immuable), il devrait donc créer et supprimer un StringBuilder sur chaque ligne. Pour la vitesse, vous devez utiliser le même StringBuilder jusqu'à ce que vous ayez fini de construire:

string a,b,c,d;
StringBuilder e = new StringBuilder();
 e.Append(b);
 e.Append(c);
 e.Append(d);
 a = e.ToString();

5
Il n'y a aucune raison pour que le compilateur C # doive traiter le deuxième échantillon différemment du premier. En particulier, il n'y a aucune obligation de produire une chaîne à la fin de chaque ligne. Le compilateur peut se comporter comme vous le dites, mais il n'est pas obligé de le faire.
CodesInChaos

@CodesInChaos, une variable de chaîne immuable est spécifiquement affectée à la fin de chaque ligne, cela ne crée-t-il pas l'obligation de produire une chaîne? Cependant, je suis d'accord avec le fait qu'il n'y a aucune raison de traiter chaque ligne individuelle différemment (et je ne suis pas sûr que ce soit le cas), la perte de performance provient cependant de la réallocation, donc cela n'aurait pas d'importance.
Saeb Amini

@SaebAmini - Si aest une variable locale et que l'objet auquel elle fait référence n'a pas été affecté à une autre variable (qui pourrait être accessible à un autre thread), un bon optimiseur pourrait déterminer que aucun autre codea n'est accessible par cette séquence de lignes; seule la valeur finale des amatières. Il pourrait donc traiter ces trois lignes de code comme si elles avaient été écrites a = b + c + d;.
ToolmakerSteve

29

StringBuilder est préférable SI vous faites plusieurs boucles ou fourchettes dans votre passe de code ... cependant, pour des performances PURE, si vous pouvez vous en sortir avec une déclaration de chaîne SINGLE , alors c'est beaucoup plus performant.

Par exemple:

string myString = "Some stuff" + var1 + " more stuff"
                  + var2 + " other stuff" .... etc... etc...;

est plus performant 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..

Dans ce cas, StringBuild pourrait être considéré comme plus maintenable, mais n'est pas plus performant que la déclaration de chaîne unique.

9 fois sur 10 cependant ... utilisez le générateur de chaînes.

Sur une note latérale: string + var est également plus performant que l'approche string.Format (généralement) qui utilise un StringBuilder en interne (en cas de doute ... vérifiez le réflecteur!)


17
Je souhaite que vous ayez dit comment vous le savez / comment le vérifier.
ChrisW

2
Vous ne vérifiez pas les performances dans le réflecteur: vous vérifiez les performances en synchronisant le code de version, analysez avec un profileur et cherchez des explications avec le réflecteur.
Albin Sunnanbo

3
Envisagez d'utiliser String.Format () pour concaténer un petit nombre de petites chaînes, en particulier si l'objectif est de formater les messages pour les montrer à l'utilisateur.
Gary Kindel

1
Ce sont de très mauvaises informations. ("la chaîne myString est plus performante") pas vrai du tout.
Tom Stickel

3
Les informations contenues dans cette réponse sont incorrectes. StringBuilder est un peu plus rapide que la concaténation effectuée dans la même instruction; cependant, vous ne remarquerez une différence entre les deux que si vous le faites des centaines de milliers de fois ( Source ). Comme dit dans la source, "ça n'a pas d'importance!"
David Sherret

25

Un exemple simple pour démontrer la différence de vitesse lors de l'utilisation de la Stringconcaténation vs 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");

Résultat:

Utilisation de la concaténation de chaînes: 15423 millisecondes

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

Résultat:

Utilisation de StringBuilder: 10 millisecondes

En conséquence, la première itération a pris 15423 ms tandis que la deuxième itération utilisant a StringBuilderpris 10 ms.

Il me semble que l'utilisation StringBuilderest plus rapide, beaucoup plus rapide.


24

Cette référence montre que la concaténation régulière est plus rapide lors de la combinaison de 3 chaînes ou moins.

http://www.chinhdo.com/20070224/stringbuilder-is-not-always-faster/

StringBuilder peut améliorer considérablement l'utilisation de la mémoire, en particulier dans le cas où vous ajoutez 500 chaînes ensemble.

Prenons l'exemple suivant:

string buffer = "The numbers are: ";
for( int i = 0; i < 5; i++)
{
    buffer += i.ToString();
}
return buffer;

Que se passe-t-il en mémoire? Les chaînes suivantes sont créées:

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"

En ajoutant ces cinq nombres à la fin de la chaîne, nous avons créé 13 objets chaîne! Et 12 d'entre eux étaient inutiles! Hou la la!

StringBuilder résout ce problème. Ce n'est pas une "chaîne modifiable" comme nous l'entendons souvent ( toutes les chaînes en .NET sont immuables ). Il fonctionne en gardant un tampon interne, un tableau de caractères. Appeler Append () ou AppendLine () ajoute la chaîne à l'espace vide à la fin du tableau char; si le tableau est trop petit, il crée un nouveau tableau plus grand et y copie le tampon. Ainsi, dans l'exemple ci-dessus, StringBuilder peut n'avoir besoin que d'un seul tableau pour contenir les 5 ajouts à la chaîne, selon la taille de son tampon. Vous pouvez indiquer à StringBuilder la taille de son tampon dans le constructeur.


2
Nit mineur: c'est un peu étrange de dire "nous avons créé 13 objets chaîne, et 12 d'entre eux étaient inutiles", puis de dire que StringBuilder résout ce problème. Après tout, six des cordes que vous n'avez pas d'autre choix que de créer; ils viennent i.ToString(). Donc, avec StringBuilder, vous devez toujours créer 6 + 1 chaînes; il a réduit la création de 13 chaînes à la création de 7 chaînes. Mais ce n'est toujours pas la bonne façon de voir les choses; la création des six chaînes de nombres n'est pas pertinente. Conclusion: vous n'auriez tout simplement pas dû mentionner les six chaînes créées par i.ToString(); ils ne font pas partie de la comparaison d'efficacité.
ToolmakerSteve

13

String Vs String Builder:

Première chose que vous devez savoir dans quelle assemblée vit ces deux classes?

Alors,

la chaîne est présente dans l' Systemespace de noms.

et

StringBuilder est présent dans l' System.Textespace de noms.

Pour la déclaration de chaîne :

Vous devez inclure l' Systemespace de noms. quelque chose comme ça. Using System;

et

Pour la déclaration StringBuilder :

Vous devez inclure l' System.textespace de noms. quelque chose comme ça. Using System.text;

Maintenant, venez la vraie question.

Quelle est la différence entre string et StringBuilder ?

La principale différence entre ces deux est que:

la chaîne est immuable.

et

StringBuilder est mutable.

Voyons maintenant la différence entre immuable et mutable

Mutable:: signifie modifiable.

Immuable:: signifie Non modifiable.

Par exemple:

using System;

namespace StringVsStrigBuilder
{
    class Program
    {
        static void Main(string[] args)
        {
            // String Example

            string name = "Rehan";
            name = name + "Shah";
            name = name + "RS";
            name = name + "---";
            name = name + "I love to write programs.";

            // Now when I run this program this output will be look like this.
            // output : "Rehan Shah RS --- I love to write programs."
        }
    }
}

Donc, dans ce cas, nous allons changer le même objet 5 fois.

Donc, la question évidente est que! Qu'est-ce qui se passe réellement sous le capot, lorsque nous changeons la même chaîne 5 fois.

C'est ce qui se passe lorsque nous changeons la même chaîne 5 fois.

regardons la figure.

entrez la description de l'image ici

Explication:

Lorsque nous initialisons pour la première fois cette variable "nom" en "Rehan", c'est-à string name = "Rehan" - dire que cette variable est créée sur la pile "nom" et pointe vers cette valeur "Rehan". après l'exécution de cette ligne: "nom = nom +" Shah ". la variable de référence ne pointe plus sur cet objet" Rehan ", elle pointe maintenant sur" Shah "et ainsi de suite.

Il en stringest de même immuable signifiant qu'une fois que nous créons l'objet dans la mémoire, nous ne pouvons pas les changer.

Ainsi, lorsque nous concaténons la namevariable, l'objet précédent reste là dans la mémoire et un autre nouvel objet chaîne est créé ...

Donc, d'après la figure ci-dessus, nous avons cinq objets, les quatre objets sont jetés, ils ne sont pas utilisés du tout. Ils restent en mémoire et ils occupent la quantité de mémoire. "Garbage Collector" est responsable de ce nettoyage si propre que les ressources de la mémoire.

Donc, en cas de chaîne, à tout moment, lorsque nous manipulons la chaîne encore et encore, nous avons de nombreux objets créés et y restons en mémoire.

Voici donc l'histoire de la chaîne Variable.

Regardons maintenant vers StringBuilder Object. Par exemple:

using System;
using System.Text;

namespace StringVsStrigBuilder
{
    class Program
    {
        static void Main(string[] args)
        {
            // StringBuilder Example

            StringBuilder name = new StringBuilder();
            name.Append("Rehan");
            name.Append("Shah");
            name.Append("RS");
            name.Append("---");
            name.Append("I love to write programs.");


            // Now when I run this program this output will be look like this.
            // output : "Rehan Shah Rs --- I love to write programs."
        }
    }
}

Donc, dans ce cas, nous allons changer le même objet 5 fois.

Donc, la question évidente est que! Ce qui se passe réellement sous le capot, lorsque nous changeons le même StringBuilder 5 fois.

C'est ce qui se passe lorsque nous changeons le même StringBuilder 5 fois.

regardons la figure. entrez la description de l'image ici

Explication: dans le cas d'un objet StringBuilder. vous n'obtiendrez pas le nouvel objet. Le même objet sera changé en mémoire donc même si vous changez d'objet et disons 10 000 fois nous n'aurons toujours qu'un seul objet stringBuilder.

Vous n'avez pas beaucoup d'objets garbage ou d'objets stringBuilder non référencés parce que cela peut être changé. Il est mutable ce qui signifie qu'il change au fil du temps?

Différences:

  • La chaîne est présente dans l'espace de noms System où Stringbuilder est présente dans l'espace de noms System.Text.
  • chaîne est immuable où StringBuilder est mutabe.

12

Oui, StringBuilderdonne de meilleures performances tout en effectuant des opérations répétées sur une chaîne. C'est parce que toutes les modifications sont apportées à une seule instance, ce qui permet de gagner beaucoup de temps au lieu de créer une nouvelle instance comme String.

String Vs Stringbuilder

  • String

    1. sous l' Systemespace de noms
    2. instance immuable (lecture seule)
    3. les performances se dégradent en cas de changement continu de valeur
    4. fil sûr
  • StringBuilder (chaîne mutable)

    1. sous l' System.Textespace de noms
    2. instance mutable
    3. affiche de meilleures performances car de nouvelles modifications sont apportées à l'instance existante

Je recommande vivement l'article dotnet mob: String Vs StringBuilder en C # .

Question de dépassement de pile associée: mutabilité de la chaîne lorsque la chaîne ne change pas en C #? .


8

StringBuilder réduit le nombre d'allocations et d'affectations, au prix d'une mémoire supplémentaire utilisée. Utilisé correctement, il peut supprimer complètement la nécessité pour le compilateur d'allouer de plus en plus de chaînes jusqu'à ce que le résultat soit trouvé.

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
}

contre.

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

5

Les performances d'une opération de concaténation pour un objet String ou StringBuilder dépendent de la fréquence à laquelle une allocation de mémoire se produit. Une opération de concaténation String alloue toujours de la mémoire, tandis qu'une opération de concaténation StringBuilder n'alloue de la mémoire que si la mémoire tampon de l'objet StringBuilder est trop petite pour accueillir les nouvelles données. Par conséquent, la classe String est préférable pour une opération de concaténation si un nombre fixe d'objets String est concaténé. Dans ce cas, les opérations de concaténation individuelles peuvent même être combinées en une seule opération par le compilateur. Un objet StringBuilder est préférable pour une opération de concaténation si un nombre arbitraire de chaînes est concaténé; par exemple, si une boucle concatène un nombre aléatoire de chaînes d'entrée utilisateur.

Source: MSDN


3

StringBuilder est préférable pour construire une chaîne à partir de nombreuses valeurs non constantes.

Si vous créez une chaîne à partir d'un grand nombre de valeurs constantes, telles que plusieurs lignes de valeurs dans un document HTML ou XML ou d'autres morceaux de texte, vous pouvez vous contenter d'ajouter simplement à la même chaîne, car presque tous les compilateurs le font "pliage constant", un processus de réduction de l'arbre d'analyse lorsque vous avez un tas de manipulation constante (il est également utilisé lorsque vous écrivez quelque chose comme int minutesPerYear = 24 * 365 * 60). Et pour les cas simples avec des valeurs non constantes ajoutées les unes aux autres, le compilateur .NET réduira votre code à quelque chose de similaire à ce qui se StringBuilderpasse.

Mais lorsque votre ajout ne peut pas être réduit à quelque chose de plus simple par le compilateur, vous aurez besoin d'un StringBuilder. Comme le souligne fizch, cela est plus susceptible de se produire à l'intérieur d'une boucle.


2

Je crois que StringBuilder est plus rapide si vous avez plus de 4 chaînes que vous devez ajouter ensemble. De plus, il peut faire des choses intéressantes comme AppendLine.


2

Dans .NET, StringBuilder est toujours plus rapide que l'ajout de chaînes. Je suis presque sûr qu'en Java, ils créent simplement un StringBuffer sous le capot lorsque vous ajoutez des chaînes, donc il n'y a pas vraiment de différence. Je ne sais pas pourquoi ils ne l'ont pas encore fait dans .NET.



2

L'utilisation de chaînes pour la concaténation peut entraîner une complexité d'exécution de l'ordre de O(n^2) .

Si vous utilisez un StringBuilder, il y a beaucoup moins de copie de mémoire à faire. Avec le, StringBuilder(int capacity)vous pouvez augmenter les performances si vous pouvez estimer la taille de la finale String. Même si vous n'êtes pas précis, vous n'aurez probablement qu'à augmenter la capacité de StringBuilderquelques fois, ce qui peut également améliorer les performances.


2

J'ai vu des gains de performances significatifs en utilisant l' EnsureCapacity(int capacity)appel de méthode sur une instance de StringBuilderavant de l'utiliser pour tout stockage de chaîne. J'appelle généralement cela sur la ligne de code après instanciation. Cela a le même effet que si vous instanciez StringBuildercomme ceci:

var sb = new StringBuilder(int capacity);

Cet appel alloue la mémoire nécessaire à l'avance, ce qui entraîne moins d'allocations de mémoire lors de plusieurs Append()opérations. Vous devez faire une estimation éclairée de la quantité de mémoire dont vous aurez besoin, mais pour la plupart des applications, cela ne devrait pas être trop difficile. Je me trompe généralement du côté d'un peu trop de mémoire (nous parlons environ 1k).


Il n'est pas nécessaire d'appeler EnsureCapacityjuste après l' StringBuilderinstanciation. Instancier simplement StringBuildercomme ceci: var sb = new StringBuilder(int capacity).
David Ferenczy Rogožan

On m'a dit qu'il était utile d'utiliser un nombre premier.
Karl Gjertsen

1

Suite aux réponses précédentes, la première chose que je fais toujours quand je pense à des problèmes comme celui-ci est de créer une petite application de test. Dans cette application, effectuez un test de synchronisation pour les deux scénarios et voyez par vous-même ce qui est plus rapide.

À mon humble avis, l'ajout de plus de 500 entrées de chaîne devrait certainement utiliser StringBuilder.


1

String et StringBuilder sont en fait tous deux immuables, le StringBuilder a intégré des tampons qui permettent de gérer sa taille plus efficacement. Lorsque le StringBuilder doit être redimensionné, c'est lorsqu'il est réalloué sur le tas. Par défaut, il a une taille de 16 caractères, vous pouvez le définir dans le constructeur.

par exemple.

StringBuilder sb = nouveau StringBuilder (50);


2
Pas sûr que vous compreniez ce que signifie immuable. Les cordes sont immuables, elles NE PEUVENT PAS être modifiées. Tout ce qui a "changé" est en fait juste que l'ancienne valeur reste sur le tas sans aucun pointeur pour la localiser. StringBuilder (mutable) est le type de référence sur le tas, le pointeur vers le tas est modifié et l'allocation d'espace pour ce changement.
Tom Stickel

1

La concaténation de chaînes vous coûtera plus cher. En Java, vous pouvez utiliser StringBuffer ou StringBuilder en fonction de vos besoins. Si vous voulez une implémentation synchronisée et thread-safe, optez pour StringBuffer. Ce sera plus rapide que la concaténation de chaînes.

Si vous n'avez pas besoin d'une implémentation synchronisée ou Thread safe, optez pour StringBuilder. Ce sera plus rapide que la concaténation de chaînes et aussi plus rapide que StringBuffer car il n'y a pas de surcharge de synchronisation.


0

StringBuilder est probablement préférable. La raison en est qu'il alloue plus d'espace que nécessaire actuellement (vous définissez le nombre de caractères) pour laisser de la place pour les ajouts futurs. Ensuite, ces ajouts futurs qui tiennent dans le tampon actuel ne nécessitent aucune allocation de mémoire ou récupération de place, ce qui peut être coûteux. En général, j'utilise StringBuilder pour la concatentation de chaînes complexes ou la mise en forme multiple, puis convertis en une chaîne normale lorsque les données sont terminées, et je veux à nouveau un objet immuable.


0

Si vous faites beaucoup de concaténation de chaînes, utilisez un StringBuilder. Lorsque vous concaténez avec une chaîne, vous créez une nouvelle chaîne à chaque fois, en utilisant plus de mémoire.

Alex


0

En règle générale, si je dois définir la valeur de la chaîne plus d'une fois ou s'il y a des ajouts à la chaîne, il doit s'agir d'un générateur de chaîne. J'ai vu des applications que j'ai écrites dans le passé avant d'en apprendre davantage sur les constructeurs de chaînes qui ont eu une énorme empreinte mémoire qui semble simplement continuer de croître. La modification de ces programmes pour utiliser le générateur de chaînes a considérablement réduit l'utilisation de la mémoire. Maintenant, je le jure par le constructeur de cordes.



0

StringBuilder est nettement plus efficace mais vous ne verrez pas ces performances à moins que vous ne fassiez une grande quantité de modification de chaîne.

Vous trouverez ci-dessous un morceau de code rapide pour donner un exemple des performances. Comme vous pouvez le voir, vous ne commencez vraiment à voir une augmentation importante des performances que lorsque vous entrez dans de grandes itérations.

Comme vous pouvez le voir, les 200 000 itérations ont pris 22 secondes tandis que le million d'itérations utilisant le a StringBuilderété presque instantané.

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

Résultat du code ci-dessus:

Début String + au 28/01/2013 16:55:40.

Chaîne terminée + au 28/01/2013 16:55:40.

Début String + au 28/01/2013 16:55:40.

Chaîne terminée + au 28/01/2013 16:56:02.

Début Sb ajouté au 28/01/2013 16:56:02.

Ajout de Sb terminé au 28/01/2013 16:56:02.


1
Si String builder est si puissant, alors pourquoi nous avons String en existence. Pourquoi ne pas effacer complètement String. J'ai posé cette question afin que vous puissiez me dire les AVANTAGES de String sur StringBuilder
Incassable

-2

StringBuilder fonctionnera mieux, du point de vue de la mémoire. Quant au traitement, la différence de temps d'exécution peut être négligeable.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.