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);
    }
}
Author: Peter Mortensen, 2008-09-06

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 .

 489
Author: Tom Hawtin - tackline, 2017-10-12 12:57:00

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.

 82
Author: Eli Courtwright, 2017-05-23 12:26:31

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.

 42
Author: ckpwong, 2008-09-06 19:25:12

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!

 22
Author: Jason Cohen, 2008-09-06 19:19:52

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.

 19
Author: Marcio Aguiar, 2013-09-10 20:55:32

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
 16
Author: Paweł Adamski, 2017-09-29 08:56:14

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

  1. 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.

  2. 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:)

 4
Author: Deepak Sharma, 2017-06-05 21:15:53

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.

 2
Author: dingalapadum, 2017-03-26 15:00:44

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.

 2
Author: Niyaz, 2017-04-01 18:03:27

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.

 2
Author: Bartosz Bierkowski, 2018-07-27 14:46:12

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.

 0
Author: iamreza, 2017-06-05 21:18:03