Concaténation de chaîne: concat () vs opérateur "+"
En supposant les chaînes a et b:
a += b
a = a.concat(b)
Sous le capot, sont-ils la même chose?
Voici concat décompilé comme référence. J'aimerais également pouvoir décompiler l'opérateur +
pour voir ce que cela fait.
public String concat(String s) {
int i = s.length();
if (i == 0) {
return this;
}
else {
char ac[] = new char[count + i];
getChars(0, count, ac, 0);
s.getChars(0, i, ac, count);
return new String(0, count + i, ac);
}
}
11 answers
Non, pas tout à fait.
Premièrement, il y a une légère différence de sémantique. If a
est null
, puis a.concat(b)
jette un NullPointerException
, mais a+=b
traitera de la valeur d'origine de a
, comme si c'était null
. De plus, la méthode concat()
n'accepte que les valeurs String
tandis que l'opérateur +
convertit silencieusement l'argument en chaîne (en utilisant la méthode toString()
pour les objets). Donc, la méthode concat()
est plus stricte dans ce qu'elle accepte.
Pour regarder sous le capot, écrivez une classe simple avec a += b;
public class Concat {
String cat(String a, String b) {
a += b;
return a;
}
}
Démontez maintenant avec javap -c
(inclus dans le Sun JDK). Vous devriez voir une liste comprenant:
java.lang.String cat(java.lang.String, java.lang.String);
Code:
0: new #2; //class java/lang/StringBuilder
3: dup
4: invokespecial #3; //Method java/lang/StringBuilder."<init>":()V
7: aload_1
8: invokevirtual #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
11: aload_2
12: invokevirtual #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: invokevirtual #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/ String;
18: astore_1
19: aload_1
20: areturn
Donc, a += b
est l'équivalent de
a = new StringBuilder()
.append(a)
.append(b)
.toString();
La méthode concat
devrait être plus rapide. Cependant, avec plus de chaînes, la méthode StringBuilder
gagne, au moins en termes de performances.
Le code source de String
et StringBuilder
(et sa classe de base package-private) est disponible dans src.zip du Soleil JDK. Vous pouvez voir que vous construisez un tableau de caractères (redimensionnement si nécessaire), puis le jeter lorsque vous créez la finale String
. En pratique, l'allocation de mémoire est étonnamment rapide.
Mise à jour: Comme le note Pawel Adamski, les performances ont changé dans HotSpot plus récent. javac
produit toujours exactement le même code, mais le compilateur bytecode tricheurs. Les tests simples échouent entièrement car tout le corps du code est jeté. La somme de System.identityHashCode
(pas String.hashCode
) montre que le code StringBuffer
a un léger avantage. Sujet à changement lorsque le prochain la mise à jour est publiée, ou si vous utilisez une JVM différente. De @ lukaseder, une liste des intrinsèques de la JVM HotSpot .
Niyaz est correct, mais il convient également de noter que l'opérateur spécial + peut être converti en quelque chose de plus efficace par le compilateur Java. Java a une classe StringBuilder qui représente une chaîne mutable sans thread. Lors de l'exécution d'un tas de concaténations de chaînes, le compilateur Java convertit silencieusement
String a = b + c + d;
Dans
String a = new StringBuilder(b).append(c).append(d).toString();
Ce qui pour les grandes chaînes est nettement plus efficace. Pour autant que je sache, cela ne se produit pas lorsque vous utilisez le concat méthode.
Cependant, la méthode concat est plus efficace lors de la concaténation d'une chaîne vide sur une chaîne existante. Dans ce cas, la JVM n'a pas besoin de créer un nouvel objet String et peut simplement renvoyer l'objet existant. Voir la documentation concat pour le confirmer.
Donc, si vous êtes super préoccupé par l'efficacité, vous devez utiliser la méthode concat lors de la concaténation de chaînes éventuellement vides, et utiliser + sinon. Cependant, la différence de performance devrait être négligeable et vous ne devriez probablement jamais vous inquiéter à ce sujet.
J'ai exécuté un test similaire à @marcio mais avec la boucle suivante à la place:
String c = a;
for (long i = 0; i < 100000L; i++) {
c = c.concat(b); // make sure javac cannot skip the loop
// using c += b for the alternative
}
Juste pour faire bonne mesure, j'ai également jeté StringBuilder.append()
. Chaque test a été exécuté 10 fois, avec 100 000 répétitions pour chaque exécution. Voici les résultats:
-
StringBuilder
gagne haut la main. Le résultat de temps d'horloge était 0 pour la plupart des courses, et le plus long a pris 16ms. -
a += b
prend environ 40000 ms (40 s) pour chaque exécution. -
concat
ne nécessite que 10000 ms (10 s) par exécution.
Je n'ai pas décompilé la classe pour voir les internes ou l'exécuter via profiler encore, mais je soupçonne a += b
passe beaucoup de temps à créer de nouveaux objets de StringBuilder
, puis à les convertir en String
.
Tom a raison de décrire exactement ce que fait l'opérateur+. Il crée un StringBuilder
temporaire, ajoute les pièces et se termine par toString()
.
Cependant, toutes les réponses jusqu'à présent ignorent les effets des optimisations d'exécution de HotSpot. Plus précisément, ces opérations temporaires sont reconnues comme un modèle commun et sont remplacées par du code machine plus efficace au moment de l'exécution.
@marcio: Vous avez créé un micro-benchmark ; avec les JVM modernes, ce n'est pas un moyen valide de code de profil.
La raison pour laquelle l'optimisation de l'exécution est importante est que beaucoup de ces différences de code-même y compris la création d'objets-sont complètement différentes une fois que HotSpot se met en marche. La seule façon de savoir avec certitude est de profiler votre code in situ.
Enfin, toutes ces méthodes sont en fait incroyablement rapide. Cela pourrait être un cas d'optimisation prématurée. Si vous avez du code qui concatène beaucoup les chaînes, le moyen d'obtenir une vitesse maximale n'a probablement rien à voir avec quels opérateurs vous choisissez et à la place l'algorithme que vous utilisez!
Que diriez-vous de quelques tests simples? Utilisé le code ci-dessous:
long start = System.currentTimeMillis();
String a = "a";
String b = "b";
for (int i = 0; i < 10000000; i++) { //ten million times
String c = a.concat(b);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
- Le
"a + b"
version exécutée dans 2500ms. - Le
a.concat(b)
exécuté dans 1200ms.
Testé plusieurs fois. L'exécution de la version concat()
a pris la moitié du temps en moyenne.
Ce résultat m'a surpris car la méthode concat()
crée toujours une nouvelle chaîne (elle renvoie un "new String(result)
". Il est bien connu que:
String a = new String("a") // more than 20 times slower than String a = "a"
Pourquoi le compilateur n'était-il pas capable d'optimiser la chaîne création dans le code "a + b", sachant que cela a toujours abouti à la même chaîne? Cela pourrait éviter une nouvelle création de chaîne. Si vous ne croyez pas la déclaration ci-dessus, testez votre auto.
La plupart des réponses ici datent de 2008. Il semble que les choses ont changé au fil du temps. Mes derniers benchmarks réalisés avec JMH montrent que sur Java 8 +
est environ deux fois plus rapide que concat
.
Mon indice:
@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
public class StringConcatenation {
@org.openjdk.jmh.annotations.State(Scope.Thread)
public static class State2 {
public String a = "abc";
public String b = "xyz";
}
@org.openjdk.jmh.annotations.State(Scope.Thread)
public static class State3 {
public String a = "abc";
public String b = "xyz";
public String c = "123";
}
@org.openjdk.jmh.annotations.State(Scope.Thread)
public static class State4 {
public String a = "abc";
public String b = "xyz";
public String c = "123";
public String d = "!@#";
}
@Benchmark
public void plus_2(State2 state, Blackhole blackhole) {
blackhole.consume(state.a+state.b);
}
@Benchmark
public void plus_3(State3 state, Blackhole blackhole) {
blackhole.consume(state.a+state.b+state.c);
}
@Benchmark
public void plus_4(State4 state, Blackhole blackhole) {
blackhole.consume(state.a+state.b+state.c+state.d);
}
@Benchmark
public void stringbuilder_2(State2 state, Blackhole blackhole) {
blackhole.consume(new StringBuilder().append(state.a).append(state.b).toString());
}
@Benchmark
public void stringbuilder_3(State3 state, Blackhole blackhole) {
blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).toString());
}
@Benchmark
public void stringbuilder_4(State4 state, Blackhole blackhole) {
blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).append(state.d).toString());
}
@Benchmark
public void concat_2(State2 state, Blackhole blackhole) {
blackhole.consume(state.a.concat(state.b));
}
@Benchmark
public void concat_3(State3 state, Blackhole blackhole) {
blackhole.consume(state.a.concat(state.b.concat(state.c)));
}
@Benchmark
public void concat_4(State4 state, Blackhole blackhole) {
blackhole.consume(state.a.concat(state.b.concat(state.c.concat(state.d))));
}
}
Résultats:
Benchmark Mode Cnt Score Error Units
StringConcatenation.concat_2 thrpt 50 24908871.258 ± 1011269.986 ops/s
StringConcatenation.concat_3 thrpt 50 14228193.918 ± 466892.616 ops/s
StringConcatenation.concat_4 thrpt 50 9845069.776 ± 350532.591 ops/s
StringConcatenation.plus_2 thrpt 50 38999662.292 ± 8107397.316 ops/s
StringConcatenation.plus_3 thrpt 50 34985722.222 ± 5442660.250 ops/s
StringConcatenation.plus_4 thrpt 50 31910376.337 ± 2861001.162 ops/s
StringConcatenation.stringbuilder_2 thrpt 50 40472888.230 ± 9011210.632 ops/s
StringConcatenation.stringbuilder_3 thrpt 50 33902151.616 ± 5449026.680 ops/s
StringConcatenation.stringbuilder_4 thrpt 50 29220479.267 ± 3435315.681 ops/s
Fondamentalement, il existe deux différences importantes entre + et la méthode concat
.
-
Si vous utilisez la méthode concat, vous ne pourrez concaténer que des chaînes + opérateur, vous pouvez également concaténer la chaîne avec n'importe quel type de données.
Par Exemple:
String s = 10 + "Hello";
Dans ce cas, la sortie doit être 10Hello.
String s = "I"; String s1 = s.concat("am").concat("good").concat("boy"); System.out.println(s1);
Dans le cas ci-dessus, vous devez fournir deux chaînes obligatoire.
-
La deuxième et principale différence entre + et concat, c'est que:
Cas 1: Supposons que je concatte les mêmes chaînes avec concat opérateur de cette façon
String s="I"; String s1=s.concat("am").concat("good").concat("boy"); System.out.println(s1);
Dans ce cas, le nombre total d'objets créés dans le pool est de 7 comme ceci:
I am good boy Iam Iamgood Iamgoodboy
Cas 2:
Maintenant, je vais concatinate les mêmes chaînes via + opérateur
String s="I"+"am"+"good"+"boy"; System.out.println(s);
Dans le cas ci-dessus nombre total de les objets créés ne sont que 5.
En fait, lorsque nous concatinons les chaînes via + opérateur puis il maintient une classe StringBuffer pour effectuer la même tâche comme suit: -
StringBuffer sb = new StringBuffer("I"); sb.append("am"); sb.append("good"); sb.append("boy"); System.out.println(sb);
De cette façon, il ne créera que cinq objets.
Alors les gars ce sont les différences fondamentales entre + et la concat méthode. Profitez de:)
Par souci d'exhaustivité, je voulais ajouter que la définition de l'opérateur " + " peut être trouvé dans le JLS SE8 15.18.1:
Si une seule expression d'opérande est de type String, alors string la conversion (§5.1.11) est effectuée sur l'autre opérande pour produire un chaîne au moment de l'exécution.
Le résultat de la concaténation de chaîne est une référence à un objet String c'est la concaténation des deux chaînes d'opérandes. Caractère de gauche opérande précèdent les caractères de la main droite opérande dans la chaîne nouvellement créée.
L'objet String est nouvellement créé (§12.5) sauf si l'expression est un expression constante (§15.28).
À propos de l'implémentation, le JLS dit ce qui suit:
Une implémentation peut choisir d'effectuer une conversion et une concaténation en une étape pour éviter de créer puis de jeter un intermédiaire Objet de type String. Pour augmenter les performances de la chaîne répétée concaténation, un compilateur Java peut utiliser la classe StringBuffer ou un technique similaire pour réduire le nombre d'objets Chaîne intermédiaire qui sont créés par l'évaluation d'une expression.
Pour les types primitifs, une implémentation peut également optimiser création d'un objet wrapper en convertissant directement à partir d'une primitive tapez sur une chaîne.
Donc, à en juger par le ' un compilateur Java peut utiliser la classe StringBuffer ou une technique similaire à reduce', différents compilateurs pourraient produire du code octet différent.
L'opérateur + peut fonctionner entre une chaîne et une valeur de type de données string, char, integer, double ou float. Il convertit simplement la valeur en sa représentation de chaîne avant la concaténation.
L'opérateur concat ne peut être fait que sur et avec des chaînes. Il vérifie la compatibilité des types de données et génère une erreur si elles ne correspondent pas.
Sauf ceci, le code que vous avez fourni fait la même chose.
Je ne pense pas.
a.concat(b)
est implémenté dans String et je pense que l'implémentation n'a pas beaucoup changé depuis les premières machines java. L'implémentation de l'opération +
dépend de la version Java et du compilateur. Actuellement, +
est implémenté en utilisant StringBuffer
pour rendre l'opération aussi rapide que possible. Peut-être que dans l'avenir, cela va changer. Dans les versions antérieures de java +
, l'opération sur les chaînes était beaucoup plus lente car elle produisait des résultats intermédiaires.
Je suppose que +=
est implémenté en utilisant +
et optimisé de manière similaire.
Lors de l'utilisation de +, la vitesse diminue à mesure que la longueur de la chaîne augmente, mais lors de l'utilisation de concat, la vitesse est plus stable, et la meilleure option est d'utiliser la classe StringBuilder qui a une vitesse stable pour le faire.
Je suppose que vous pouvez comprendre pourquoi. Mais la meilleure façon de créer des chaînes longues est d'utiliser StringBuilder () et append (), soit la vitesse sera inacceptable.