Question Concaténation de chaînes: opérateur concat () vs "+"


En supposant que la chaîne a et b:

a += b
a = a.concat(b)

Sous le capot, sont-ils la même chose?

Ici, concat est décompilé en référence. Je voudrais pouvoir décompiler le + opérateur ainsi de 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);
    }
}

412
2017-09-06 16:08


origine


Réponses:


Non, pas tout à fait.

Premièrement, il y a une légère différence dans la sémantique. Si a est null, puis a.concat(b) jette un NullPointerException mais a+=b traitera la valeur d'origine de a Comme si c'était null. De plus, le concat() méthode accepte seulement String valeurs alors que le + l'opérateur convertira silencieusement l'argument en chaîne (en utilisant le toString() méthode pour objets). Alors le concat() la méthode 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;
    }
}

Maintenant démonter 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

Alors, 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 méthode gagne, au moins en termes de performance.

Le code source de String et StringBuilder (et sa classe de base package-private) est disponible dans src.zip du Sun JDK. Vous pouvez voir que vous construisez un tableau char (redimensionnement si nécessaire), puis le jeter lorsque vous créez la finale String. En pratique, l'allocation de mémoire est étonnamment rapide.

Mettre à jour: Comme le note Pawel Adamski, la performance a changé dans HotSpot plus récent. javac produit toujours exactement le même code, mais le compilateur bytecode triche. Les tests simples échouent complètement car le corps entier du code est jeté. Sommation System.identityHashCode (ne pas String.hashCode) montre le StringBuffer le code a un léger avantage. Sous réserve de modification lorsque la prochaine mise à jour est publiée ou si vous utilisez une autre machine virtuelle Java. De @lukaseder, une liste des intrinsèques de la JVM HotSpot.


480
2017-09-06 16:25



Niyaz est correct, mais il est également intéressant 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 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 significativement plus efficace. Pour 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 l'objet existant. Voir la documentation de concat pour confirmer cela.

Donc, si vous êtes très soucieux d'efficacité, vous devriez 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 en soucier.


81
2017-09-06 16:24



J'ai effectué 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 jeté StringBuilder.append() ainsi que. Chaque test a été exécuté 10 fois, avec 100k répétitions pour chaque exécution. Voici les résultats:

  • StringBuilder gagne les mains. Le temps de l'horloge était de 0 pour la plupart des essais et le plus long de 16 ms.
  • a += b prend environ 40000ms (40s) pour chaque course.
  • concat ne nécessite que 10000ms (10s) par cycle.

Je n'ai pas encore décompilé la classe pour voir les internes ou la passer par profiler, mais je suspecte a += b passe beaucoup de temps à créer de nouveaux objets de StringBuilder puis les convertir en String.


42
2017-09-06 19:25



Tom a raison de décrire exactement ce que fait l'opérateur +. Il crée un temporaire StringBuilder, ajoute les pièces et finit avec toString().

Cependant, toutes les réponses ignorent jusqu'à présent 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 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 créer un code de profil.

La raison pour laquelle l'optimisation de l'exécution est importante est que beaucoup de ces différences de code, y compris la création d'objets, sont complètement différentes une fois que HotSpot est en cours. La seule façon de savoir avec certitude est de profiler votre code in situ.

Enfin, toutes ces méthodes sont incroyablement rapides. Cela pourrait être un cas d'optimisation prématurée. Si vous avez un code qui concatène beaucoup de chaînes, le moyen d'obtenir la vitesse maximale n'a probablement rien à voir avec les opérateurs que vous choisissez et à la place l'algorithme que vous utilisez!


21
2017-09-06 18:38



Qu'en est-il des 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é en 1200ms.

Testé plusieurs fois. le concat() l'exécution de la version a pris la moitié du temps en moyenne.

Ce résultat m'a surpris parce que le concat() La méthode 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'a-t-il pas pu optimiser la création de la chaîne en code "a + b", sachant que le résultat était 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 vous-même.


18
2017-09-06 17:59



La plupart des réponses proviennent de 2008. Il semble que les choses ont 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

15
2017-09-29 08:56



Fondamentalement, il existe deux différences importantes entre + et le concat méthode.

  1. Si vous utilisez le concat méthode alors vous ne seriez en mesure de concaténer des chaînes alors que dans le cas de la + 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, le résultat devrait ê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 obligatoires.

  2. La deuxième et principale différence entre + et concat est-ce:

    Cas 1:Supposons que je concorde les mêmes chaînes avec concat opérateur de cette manière

    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 7 comme ceci:

    I
    am
    good
    boy
    Iam
    Iamgood
    Iamgoodboy
    

    Cas 2:

    Maintenant je vais concilier 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, quand on concilie les chaînes via + opérateur alors 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.

Donc, les gars, ce sont les différences de base entre + et le concat méthode. Prendre plaisir :)


3
2017-08-19 07:49



Par souci d'exhaustivité, je voulais ajouter que la définition de l'opérateur '+' peut être trouvée dans le JLS SE8 15.18.1:

Si une seule expression d'opérande est de type String, alors une chaîne   conversion (§ 5.1.1.11) est effectuée sur l'autre opérande pour produire un   chaîne à 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 cordes d'opérande. Les personnages   de l'opérande de gauche précède 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   expression constante (§15.28).

À propos de la mise en œuvre, 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 rejeter un intermédiaire   Objet de chaîne 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 String intermédiaires   qui sont créés par l'évaluation d'une expression.

Pour les types primitifs, une implémentation peut également optimiser le   création d'un objet wrapper en convertissant directement à partir d'une primitive   tapez une chaîne.

Donc, à en juger par le 'un compilateur Java peut utiliser la classe StringBuffer ou une technique similaire à réduire', différents compilateurs pourraient produire un code octet différent.


2
2018-03-26 15:00



le + opérateur peut fonctionner entre une chaîne et une valeur de type de données string, char, integer, double ou float. Il convertit juste la valeur à sa représentation de chaîne avant la concaténation.

le opérateur de concat ne peut être fait que sur et avec des chaînes. 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.


2
2017-09-06 16:16



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 et du compilateur Java. Actuellement + est mis en œuvre en utilisant StringBuffer pour rendre l'opération aussi rapide que possible. Peut-être que dans le futur, cela va changer. Dans les versions antérieures de java + l'opération sur Strings était beaucoup plus lente car elle produisait des résultats intermédiaires.

je suppose que += est mis en œuvre en utilisant + et optimisé de manière similaire.


2
2017-09-06 16:22