Quelqu'un m'a dit qu'il était plus efficace d'utiliser StringBuffer
pour concaténer des chaînes dans Java que d'utiliser le +
opérateur pour String
s. Que se passe-t-il sous le capot lorsque vous faites cela? Qu'est-ce que StringBuffer
fait différemment?
Il est préférable d'utiliser StringBuilder (c'est une version non synchronisée; quand construisez-vous des chaînes en parallèle?) De nos jours, dans presque tous les cas, mais voici ce qui se passe:
Lorsque vous utilisez + avec deux chaînes, il compile du code comme ceci:
String third = first + second;
Pour quelque chose comme ça:
StringBuilder builder = new StringBuilder( first );
builder.append( second );
third = builder.toString();
Par conséquent, pour de petits exemples, cela ne fait généralement aucune différence. Mais lorsque vous créez une chaîne complexe, vous avez souvent beaucoup plus à faire que cela; par exemple, vous pouvez utiliser de nombreuses instructions d'ajout différentes ou une boucle comme celle-ci:
for( String str : strings ) {
out += str;
}
Dans ce cas, une nouvelle instance de StringBuilder
et une nouvelle String
(la nouvelle valeur de out
- String
s sont immuables) est requise à chaque itération. C'est très inutile. Le remplacer par un seul StringBuilder
signifie que vous pouvez simplement produire un seul String
et ne pas remplir le tas de String
s qui ne vous intéressent pas.
Pour les concaténations simples comme:
String s = "a" + "b" + "c";
Il est plutôt inutile d'utiliser StringBuffer
- comme jodonnell l'a souligné, il sera intelligemment traduit en:
String s = new StringBuffer().append("a").append("b").append("c").toString();
MAIS il est très peu performant de concaténer des chaînes dans une boucle, comme:
String s = "";
for (int i = 0; i < 10; i++) {
s = s + Integer.toString(i);
}
L'utilisation de chaîne dans cette boucle va générer 10 objets chaîne intermédiaires en mémoire: "0", "01", "012" et ainsi de suite. En écrivant la même chose en utilisant StringBuffer
, vous mettez simplement à jour un tampon interne de StringBuffer
et vous ne créez pas les objets chaîne intermédiaires dont vous n'avez pas besoin:
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 10; i++) {
sb.append(i);
}
En fait, pour l'exemple ci-dessus, vous devez utiliser StringBuilder
(introduit dans Java 1.5) au lieu de StringBuffer
- StringBuffer
est un peu plus lourd que tous ses les méthodes sont synchronisées.
L'un ne devrait pas être plus rapide que l'autre. Cela n'était pas vrai avant Java 1.4.2, car lors de la concaténation de plus de deux chaînes à l'aide de l'opérateur "+", des objets String
intermédiaires seraient créés pendant le processus de construction la chaîne finale.
Cependant, comme l'indique JavaDoc pour StringBuffer , au moins depuis Java 1.4.2 en utilisant l'opérateur "+" compile pour créer un StringBuffer
et append()
ing les nombreuses chaînes. Donc aucune différence, apparemment.
Cependant, soyez prudent lorsque vous ajoutez une chaîne à une autre dans une boucle! Par exemple:
String myString = "";
for (String s : listOfStrings) {
// Be careful! You're creating one intermediate String object
// for every iteration on the list (this is costly!)
myString += s;
}
Gardez à l'esprit, cependant, qu'en général, la concaténation de quelques chaînes avec "+" est plus propre que append()
toutes.
Sous le capot, il crée et ajoute en fait un StringBuffer, appelant toString () sur le résultat. Donc, peu importe ce que vous utilisez.
Donc
String s = "a" + "b" + "c";
devient
String s = new StringBuffer().append("a").append("b").append("c").toString();
Cela est vrai pour un tas d'annexes intégrées dans une seule instruction. Si vous créez votre chaîne au cours de plusieurs instructions, vous gaspillez de la mémoire et un StringBuffer ou StringBuilder est votre meilleur choix.
Je pense que, étant donné jdk1.5 (ou supérieur) et votre concaténation est thread-safe, vous devez utiliser StringBuilder au lieu de StringBuffer http://Java4ever.blogspot.com/2007/03/string-vs-stringbuffer-vs -stringbuilder.html Quant aux gains de vitesse: http://www.about280.com/stringtest.html
Personnellement, je coderais pour la lisibilité, donc à moins que vous ne trouviez que la concaténation de chaînes rend votre code considérablement plus lent, restez avec la méthode qui rend votre code plus lisible.
Dans certains cas, cela est obsolète en raison des optimisations effectuées par le compilateur, mais le problème général est que le code comme:
string myString="";
for(int i=0;i<x;i++)
{
myString += "x";
}
agira comme ci-dessous (chaque étape étant la prochaine itération de boucle):
Comme vous pouvez le voir, chaque itération doit copier un caractère de plus, ce qui nous oblige à effectuer 1 + 2 + 3 + 4 + 5 + ... + N opérations chaque boucle. Il s'agit d'une opération O (n ^ 2). Si toutefois nous savions à l'avance que nous n'avions besoin que de N caractères, nous pourrions le faire en une seule allocation, avec une copie de seulement N caractères des chaînes que nous utilisions - un simple O(n) opération.
StringBuffer/StringBuilder évitent cela car ils sont mutables, et n'ont donc pas besoin de continuer à copier les mêmes données encore et encore (tant qu'il y a de l'espace pour copier dans leur tampon interne). Ils évitent d'effectuer une allocation et une copie proportionnelle au nombre d'ajouts effectués en surallouant leur tampon d'une proportion de sa taille actuelle, ce qui donne un amortissement O(1) ajout).
Cependant, il convient de noter que souvent le compilateur sera en mesure d'optimiser le code dans le style StringBuilder (ou mieux - car il peut effectuer un pliage constant, etc.) automatiquement.
AFAIK cela dépend de la version de JVM, dans les versions antérieures à 1.5 utilisant "+" ou "+ =" en fait copié la chaîne entière à chaque fois.
Attention, l'utilisation de + = alloue réellement la nouvelle copie de la chaîne.
Comme cela a été souligné, l'utilisation des boucles + in implique la copie.
Lorsque les chaînes qui sont conjuguées sont des constantes de temps de compilation, elles sont concaténées au moment de la compilation, donc
String foo = "a" + "b" + "c";
Has est compilé pour:
String foo = "abc";
Java transforme string1 + string2 en une construction StringBuffer, append () et toString (). C'est logique.
Cependant, dans Java 1.4 et versions antérieures, il le ferait pour chaque + opérateur dans l'instruction séparément. Cela signifiait que faire un + b + c entraînerait deux constructions StringBuffer avec deux appels toString (). Si vous aviez une longue chaîne de concats, cela se transformerait en un vrai bordel. Le faire vous-même signifiait que vous pouviez contrôler cela et le faire correctement.
Java 5.0 et supérieur semblent le faire de manière plus sensible, c'est donc moins un problème et certainement moins bavard.
Plus d'informations:
StringBuffer est une classe thread-safe
public final class StringBuffer extends AbstractStringBuilder
implements Serializable, CharSequence
{
// .. skip ..
public synchronized StringBuffer append(StringBuffer stringbuffer)
{
super.append(stringbuffer);
return this;
}
// .. skip ..
}
Mais StringBuilder n'est pas thread-safe, il est donc plus rapide d'utiliser StringBuilder si possible
public final class StringBuilder extends AbstractStringBuilder
implements Serializable, CharSequence
{
// .. skip ..
public StringBuilder append(String s)
{
super.append(s);
return this;
}
// .. skip ..
}
StringBuffer est modifiable. Il ajoute la valeur de la chaîne à l'objet même sans instancier un autre objet. Faire quelque chose comme:
myString = myString + "XYZ"
créera un nouvea objet String.
Pour concaténer deux chaînes à l'aide de "+", une nouvelle chaîne doit être allouée avec de l'espace pour les deux chaînes, puis les données copiées à partir des deux chaînes. Un StringBuffer est optimisé pour la concaténation et alloue plus d'espace que nécessaire initialement. Lorsque vous concaténez une nouvelle chaîne, dans la plupart des cas, les caractères peuvent simplement être copiés à la fin du tampon de chaîne existant.
.
La classe StringBuffer conserve un tableau de caractères pour contenir le contenu des chaînes que vous concaténez, tandis que la méthode + crée une nouvelle chaîne à chaque appel et ajoute les deux paramètres (param1 + param2).
Le StringBuffer est plus rapide car 1. il pourrait être en mesure d'utiliser son tableau déjà existant pour concaténer/stocker toutes les chaînes. 2. même s'ils ne rentrent pas dans le tableau, il est plus rapide d'allouer un plus grand tableau de support que de générer de nouveaux objets String pour chaque évocation.
Les chaînes étant immuables, chaque appel à l'opérateur + crée un nouvel objet String et copie les données String dans la nouvelle chaîne. Étant donné que la copie d'une chaîne prend du temps linéaire dans la longueur de la chaîne, une séquence de N appels à l'opérateur + entraîne O (N2) durée de fonctionnement (quadratique).
Inversement, puisqu'un StringBuffer est modifiable, il n'a pas besoin de copier la chaîne à chaque fois que vous effectuez un Append (), donc une séquence de N appels Append () prend O(N) time ( Cela ne fait une différence significative dans l'exécution que si vous ajoutez un grand nombre de chaînes ensemble.
Comme dit, l'objet String est ummutable, ce qui signifie qu'une fois qu'il est créé (voir ci-dessous), il ne peut pas être modifié.
String x = new String ("quelque chose"); // ou
Chaîne x = "quelque chose";
Ainsi, lorsque vous essayez de concilier des objets String, la valeur de ces objets est prise et placée dans un nouvel objet String.
Si vous utilisez plutôt le StringBuffer, qui IS mutable, vous ajoutez continuellement les valeurs à une liste interne de char (primitives), qui peut être étendue ou tronquée pour correspondre à la valeur requise. Aucun nouvel objet sont créés, seuls les nouveaux caractères sont créés/supprimés lorsque cela est nécessaire pour conserver les valeurs.
Lorsque vous concaténez deux chaînes, vous créez en fait un troisième objet String en Java. L'utilisation de StringBuffer (ou StringBuilder dans Java 5/6), est plus rapide car il utilise un tableau interne de caractères pour stocker la chaîne, et lorsque vous utilisez l'une de ses méthodes d'ajout (...) , il ne crée pas de nouvel objet String. Au lieu de cela, StringBuffer/Buider ajoute le tableau interne.
Dans les concaténations simples, ce n'est pas vraiment un problème que vous concaténiez des chaînes à l'aide de StringBuffer/Builder ou de l'opérateur '+', mais lorsque vous effectuez beaucoup de concaténations de chaînes, vous verrez que l'utilisation d'un StringBuffer/Builder est beaucoup plus rapide.
Les chaînes étant immuables en Java, chaque fois que vous concanez une chaîne, un nouvel objet est créé en mémoire. StringBuffer utilise le même objet en mémoire.
Je pense que la réponse la plus simple est: c'est plus rapide.
Si vous voulez vraiment connaître toutes les choses sous le capot, vous pouvez toujours consulter la source vous-même:
La section Opérateur de concaténation de chaînes + de la spécification de langage Java) vous donne plus d'informations sur les raisons pour lesquelles l'opérateur + peut être si lent.