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);
    }
}
Était-ce utile?

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.

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

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top