Concaténation de chaînes :concat() vs opérateur « + »
-
09-06-2019 - |
Question
En supposant que les chaînes a et b :
a += b
a = a.concat(b)
Sous le capot, c'est la même chose ?
Voici concat décompilé comme référence.J'aimerais pouvoir décompiler le +
opérateur également 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);
}
}
La solution
Non, pas tout à fait.
Premièrement, il y a une légère différence de sémantique.Si a
est null
, alors a.concat(b)
jette un NullPointerException
mais a+=b
traitera la valeur originale de a
Comme si c'était null
.Par ailleurs, le concat()
la méthode accepte uniquement String
valeurs tandis que le +
L'opérateur convertira silencieusement l'argument en chaîne (en utilisant le toString()
méthode pour les objets).Alors le concat()
la méthode est plus stricte dans ce qu’elle accepte.
Pour regarder sous le capot, écrivez un cours simple avec a += b;
public class Concat {
String cat(String a, String b) {
a += b;
return a;
}
}
Maintenant démontez 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();
Le concat
la méthode devrait être plus rapide.Cependant, avec plus de chaînes, le StringBuilder
la méthode gagne, du moins en termes de performances.
Le code source de String
et StringBuilder
(et sa classe de base privée de package) est disponible dans src.zip du Sun JDK.Vous pouvez voir que vous construisez un tableau de caractères (en le redimensionnant si nécessaire), puis que vous le jetez lorsque vous créez le fichier final. 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 de bytecode triche.Les tests simples échouent complètement car l’intégralité du corps du code est supprimée.Résumer System.identityHashCode
(pas String.hashCode
) montre le StringBuffer
le code a un léger avantage.Sous réserve de changement lors de la publication de la prochaine mise à jour ou si vous utilisez une autre JVM.Depuis @lukaseder, une liste des éléments intrinsèques de HotSpot JVM.
Autres conseils
Niaz 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 non thread-safe.Lors de l'exécution d'un ensemble 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 grosses cordes est nettement plus efficace.Autant que je sache, cela ne se produit pas lorsque vous utilisez la méthode concat.
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 celui existant.Voir la documentation de concaténation pour le confirmer.
Donc, si vous êtes très 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 performances devrait être négligeable et vous ne devriez probablement jamais vous en soucier.
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 ajouté StringBuilder.append()
aussi.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 du temps d'horloge était de 0 pour la plupart des exécutions, et la plus longue a duré 16 ms.a += b
prend environ 40 000 ms (40 s) pour chaque exécution.concat
ne nécessite que 10 000 ms (10 s) par exécution.
Je n'ai pas encore décompilé la classe pour voir les éléments internes ni l'exécuter via le profileur, mais je soupçonne a += b
passe une grande partie de son temps à créer de nouveaux objets de StringBuilder
puis les reconvertir en String
.
La plupart des réponses ici datent de 2008.Il semble que les choses aient changé avec le temps.Mes derniers benchmarks réalisés avec JMH montrent que sur Java 8 +
est environ deux fois plus rapide que concat
.
Mon benchmark :
@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
Tom a raison de décrire exactement ce que fait l'opérateur +.Cela crée un temporaire StringBuilder
, ajoute les parties et termine par toString()
.
Cependant, jusqu’à présent, toutes les réponses ignorent les effets des optimisations d’exécution de HotSpot.Plus précisément, ces opérations temporaires sont reconnues comme un modèle courant et sont remplacées par un 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 valable de profiler le code.
La raison pour laquelle l'optimisation de l'exécution est importante est que bon nombre de ces différences dans le code - y compris même la création d'objets - sont complètement différentes une fois HotSpot lancé.La seule façon d'en être sûr est de profiler votre code sur place.
Enfin, toutes ces méthodes sont en fait incroyablement rapides.Il s’agit peut-être d’un cas d’optimisation prématurée.Si votre code concatène beaucoup de chaînes, la manière d'obtenir une vitesse maximale n'a probablement rien à voir avec les opérateurs que vous choisissez mais plutôt avec l'algorithme que vous utilisez !
Que diriez-vous de quelques tests simples ?J'ai 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 2500 ms. - Le
a.concat(b)
exécuté dans 1200 ms.
Testé plusieurs fois.Le concat()
l'exécution de la version prenait en moyenne la moitié du temps.
Ce résultat m'a surpris car le concat()
La méthode crée toujours une nouvelle chaîne (elle renvoie un "new String(result)
".C'est bien connu :
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 création de chaîne dans le code "a + b", sachant que cela aboutissait toujours à 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 par vous-même.
Fondamentalement, il existe deux différences importantes entre + et le concat
méthode.
Si vous utilisez le concaténer alors vous ne pourrez concaténer des chaînes que dans le cas + 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 10Bonjour.
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 obligatoires.
La deuxième et principale différence entre + et concaténer est-ce:
Cas 1:Supposons que je concatène les mêmes chaînes avec concaténer 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 concatiner les mêmes chaînes via + opérateur
String s="I"+"am"+"good"+"boy"; System.out.println(s);
Dans le cas ci-dessus, le nombre total d'objets créés n'est que de 5.
En fait, lorsque nous concatinons les chaînes via + opérateur, 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, seuls cinq objets seront créés.
Alors les gars, voici les différences fondamentales entre + et le concaténer méthode.Apprécier :)
Par souci d'exhaustivité, je voulais ajouter que la définition de l'opérateur '+' se trouve dans le JLS SE8 15.18.1:
Si une seule expression de l'opérande est de type chaîne de type, la conversion de chaîne (§5.1.11) est effectuée sur l'autre opérande pour produire une chaîne au moment de l'exécution.
Le résultat de la concaténation des chaînes est une référence à un objet de chaîne qui est la concaténation des deux chaînes d'opérande.Les caractères de l'opérande de gauche précèdent les caractères de l'opérande droit dans la chaîne nouvellement créée.
L'objet String est nouvellement créé (§12.5) à moins que l'expression soit une 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 objet de chaîne intermédiaire.Pour augmenter les performances de la concaténation des chaînes répétées, un compilateur Java peut utiliser la classe StringBuffer ou une technique similaire pour réduire le nombre d'objets de chaîne intermédiaires qui sont créés par évaluation d'une expression.
Pour les types primitifs, une implémentation peut également optimiser la création d'un objet wrapper en convertissant directement d'un type primitif en une chaîne.
Ainsi, à en juger par « un compilateur Java peut utiliser la classe StringBuffer ou une technique similaire pour réduire », différents compilateurs pourraient produire un byte-code différent.
Le + opérateur peut fonctionner entre une chaîne et une valeur de type de données chaîne, char, entier, double ou flottant.Il convertit simplement la valeur en sa représentation sous forme de chaîne avant la concaténation.
Le opérateur de concaténation ne peut se faire que sur et avec des cordes.Il vérifie la compatibilité des types de données et renvoie une erreur s'ils ne correspondent pas.
Sauf que 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.Le +
la mise en œuvre de l'opération dépend de la version Java et du compilateur.Actuellement +
est mis en œuvre en utilisant StringBuffer
pour que l'opération soit la plus rapide possible.Peut-être qu’à l’avenir, cela changera.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 mis en œuvre en utilisant +
et optimisé de la même manière.
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 consiste à utiliser la classe StringBuilder qui a une vitesse stable pour ce faire.
Je suppose que vous pouvez comprendre pourquoi.Mais le meilleur moyen de créer de longues chaînes consiste à utiliser StringBuilder() et append(), l'une ou l'autre vitesse sera inacceptable.