Question

Etant donné les 2 implémentations toString() ci-dessous, dont l'un est préféré:

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}

ou

public String toString(){
    StringBuilder sb = new StringBuilder(100);
    return sb.append("{a:").append(a)
          .append(", b:").append(b)
          .append(", c:").append(c)
          .append("}")
          .toString();
}

Plus important encore, étant donné que nous avons seulement trois propriétés, il ne pourrait pas faire une différence, mais à quel moment voulez-vous passer de + concat à StringBuilder?

Était-ce utile?

La solution

La version 1 est préférable car il est plus court et le compilateur en fait transformer en version 2 - aucune différence de performance que ce soit

.
  

Plus important étant donné que nous avons seulement 3   propriétés il pourrait ne pas faire   différence, mais à quel moment avez-vous   passer de concat au constructeur?

Au point où vous concaténer dans une boucle -. Qui est généralement lorsque le compilateur ne peut pas se substituer StringBuilder par lui-même

Autres conseils

La clé est de savoir si vous écrivez un seul concaténation en un seul endroit ou l'accumuler au fil du temps.

Pour l'exemple que vous avez donné, il n'y a pas d'intérêt à utiliser explicitement StringBuilder. (Regardez le code compilé pour votre premier cas.)

Mais si vous construisez une chaîne par exemple l'intérieur d'une boucle, utilisez StringBuilder.

Pour clarifier les choses, en supposant que hugeArray contient des milliers de chaînes, le code comme ceci:

...
String result = "";
for (String s : hugeArray) {
    result = result + s;
}

est très gaspillage de temps et de mémoire par rapport à:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();

Je préfère:

String.format( "{a: %s, b: %s, c: %s}", a, b, c );

... parce qu'il est court et facile à lire.

Je voudrais pas optimiser cette vitesse pour moins que vous l'utilisez dans une boucle avec une très haute répétition nombre et ont mesuré la différence de performance.

Je suis d'accord, que si vous devez produire un grand nombre de paramètres, ce formulaire peut être déroutante (comme l'un des commentaires disent). Dans ce cas, je passe à une forme plus lisible (en utilisant peut-être ToStringBuilder de apache-commons -. pris de la réponse du mat b) et ignorer à nouveau la performance

Dans la plupart des cas, vous ne verrez pas une différence réelle entre les deux approches, mais il est facile de construire un scénario pire des cas comme celui-ci:

public class Main
{
    public static void main(String[] args)
    {
        long now = System.currentTimeMillis();
        slow();
        System.out.println("slow elapsed " + (System.currentTimeMillis() - now) + " ms");

        now = System.currentTimeMillis();
        fast();
        System.out.println("fast elapsed " + (System.currentTimeMillis() - now) + " ms");
    }

    private static void fast()
    {
        StringBuilder s = new StringBuilder();
        for(int i=0;i<100000;i++)
            s.append("*");      
    }

    private static void slow()
    {
        String s = "";
        for(int i=0;i<100000;i++)
            s+="*";
    }
}

La sortie est la suivante:

slow elapsed 11741 ms
fast elapsed 7 ms

Le problème est que pour + = ajouter à une chaîne recompose une nouvelle chaîne, il coûte quelque chose linéaire à la longueur de vos cordes (somme des deux).

- à votre question:

La deuxième approche serait plus rapide, mais il est moins lisible et plus difficile à maintenir. Comme je l'ai dit, dans votre cas, vous verrez probablement pas la différence.

J'ai aussi clash avec mon patron sur le fait que ce soit d'utiliser append ou + .Comme ils utilisent Append (je ne peux pas encore de chiffre comme ils disent à chaque fois qu'un nouvel objet est créé). Donc, je pensais faire un peu de R & D.Although J'adore Michael Borgwardt mais je voulais juste explaination montrer une explication si quelqu'un va vraiment besoin de connaître à l'avenir.

/**
 *
 * @author Perilbrain
 */
public class Appc {
    public Appc() {
        String x = "no name";
        x += "I have Added a name" + "We May need few more names" + Appc.this;
        x.concat(x);
        // x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
        //System.out.println(x);
    }

    public void Sb() {
        StringBuilder sbb = new StringBuilder("no name");
        sbb.append("I have Added a name");
        sbb.append("We May need few more names");
        sbb.append(Appc.this);
        sbb.append(sbb.toString());
        // System.out.println(sbb.toString());
    }
}

et le démontage de la classe ci-dessus est comme

 .method public <init>()V //public Appc()
  .limit stack 2
  .limit locals 2
met001_begin:                                  ; DATA XREF: met001_slot000i
  .line 12
    aload_0 ; met001_slot000
    invokespecial java/lang/Object.<init>()V
  .line 13
    ldc "no name"
    astore_1 ; met001_slot001
  .line 14

met001_7:                                      ; DATA XREF: met001_slot001i
    new java/lang/StringBuilder //1st object of SB
    dup
    invokespecial java/lang/StringBuilder.<init>()V
    aload_1 ; met001_slot001
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    ldc "I have Added a nameWe May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    aload_0 ; met001_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    astore_1 ; met001_slot001
  .line 15
    aload_1 ; met001_slot001
    aload_1 ; met001_slot001
    invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
g;
    pop
  .line 18
    return //no more SB created
met001_end:                                    ; DATA XREF: met001_slot000i ...

; ===========================================================================

;met001_slot000                                ; DATA XREF: <init>r ...
    .var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001                                ; DATA XREF: <init>+6w ...
    .var 1 is x Ljava/lang/String; from met001_7 to met001_end
  .end method
;44-1=44
; ---------------------------------------------------------------------------


; Segment type: Pure code
  .method public Sb()V //public void Sb
  .limit stack 3
  .limit locals 2
met002_begin:                                  ; DATA XREF: met002_slot000i
  .line 21
    new java/lang/StringBuilder
    dup
    ldc "no name"
    invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    astore_1 ; met002_slot001
  .line 22

met002_10:                                     ; DATA XREF: met002_slot001i
    aload_1 ; met002_slot001
    ldc "I have Added a name"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 23
    aload_1 ; met002_slot001
    ldc "We May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 24
    aload_1 ; met002_slot001
    aload_0 ; met002_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    pop
  .line 25
    aload_1 ; met002_slot001
    aload_1 ; met002_slot001
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 28
    return
met002_end:                                    ; DATA XREF: met002_slot000i ...


;met002_slot000                                ; DATA XREF: Sb+25r
    .var 0 is this LAppc; from met002_begin to met002_end
;met002_slot001                                ; DATA XREF: Sb+9w ...
    .var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
  .end method
;96-49=48
; ---------------------------------------------------------------------------

Vous pouvez voir les deux codes ci-dessus Michael est right.In chaque cas, un seul objet SB est créé.

Depuis Java 1.5, simple concaténation ligne avec "+" et StringBuilder.append () générer exactement le même bytecode.

Donc, pour des raisons de lisibilité du code, utilisez "+".

2 exceptions:

  • environnement multithread: StringBuffer
  • concaténation dans les boucles: StringBuilder / StringBuffer

Utilisation dernière version de Java (1.8) le démontage (javap -c) montre l'optimisation mis en place par le compilateur. + ainsi sb.append() va générer un code très similaire. Cependant, cela vaut la peine d'inspecter le comportement si nous utilisons + dans une boucle.

Ajout de chaînes en utilisant + dans une boucle

Java:

public String myCatPlus(String[] vals) {
    String result = "";
    for (String val : vals) {
        result = result + val;
    }
    return result;
}

ByteCode: (extrait de la boucle de for)

12: iload         5
14: iload         4
16: if_icmpge     51
19: aload_3
20: iload         5
22: aaload
23: astore        6
25: new           #3                  // class java/lang/StringBuilder
28: dup
29: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload         6
38: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc          5, 1
48: goto          12

Ajout des chaînes à l'aide stringbuilder.append

Java:

public String myCatSb(String[] vals) {
    StringBuilder sb = new StringBuilder();
    for(String val : vals) {
        sb.append(val);
    }
    return sb.toString();
}

ByteCdoe: (extrait de la boucle de for)

17: iload         5
19: iload         4
21: if_icmpge     43
24: aload_3
25: iload         5
27: aaload
28: astore        6
30: aload_2
31: aload         6
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc          5, 1
40: goto          17
43: aload_2

Il y a un peu de différence flagrante si. En premier cas, où + a été utilisé, nouveau StringBuilder est créé pour chaque itération de boucle et le résultat généré est stocké en faisant un appel de toString() (29 à 41). Donc, vous générer des chaînes intermédiaires que votre vraiment pas besoin tout en utilisant l'opérateur de + en boucle for.

En Java 9 la version 1 devrait être plus rapide car il est converti en invokedynamic appel. Plus de détails peuvent être trouvés dans Jep-280 :

  

L'idée est de remplacer l'ensemble de la danse append StringBuilder avec un simple appel invokedynamic à java.lang.invoke.StringConcatFactory, qui accepte les valeurs de la nécessité de concaténation.

Pour des raisons de performance, l'utilisation de += (concaténation de String) est découragée. La raison pour laquelle est: Java String est immuable, chaque fois qu'une nouvelle concaténation se fait une nouvelle String est créée (la nouvelle a une autre empreinte de la plus ancienne déjà dans la piscine chaîne ). La création de nouvelles chaînes exerce une pression sur le GC et ralentit le programme: création d'un objet est cher.

Ci-dessous le code devrait le rendre plus pratique et clair en même temps.

public static void main(String[] args) 
{
    // warming up
    for(int i = 0; i < 100; i++)
        RandomStringUtils.randomAlphanumeric(1024);
    final StringBuilder appender = new StringBuilder();
    for(int i = 0; i < 100; i++)
        appender.append(RandomStringUtils.randomAlphanumeric(i));

    // testing
    for(int i = 1; i <= 10000; i*=10)
        test(i);
}

public static void test(final int howMany) 
{
    List<String> samples = new ArrayList<>(howMany);
    for(int i = 0; i < howMany; i++)
        samples.add(RandomStringUtils.randomAlphabetic(128));

    final StringBuilder builder = new StringBuilder();
    long start = System.nanoTime();
    for(String sample: samples)
        builder.append(sample);
    builder.toString();
    long elapsed = System.nanoTime() - start;
    System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);

    String accumulator = "";
    start = System.nanoTime();
    for(String sample: samples)
        accumulator += sample;
    elapsed = System.nanoTime() - start;
    System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);

    start = System.nanoTime();
    String newOne = null;
    for(String sample: samples)
        newOne = new String(sample);
    elapsed = System.nanoTime() - start;
    System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}

Les résultats pour une course sont présentés ci-dessous.

builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us

builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us

builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us

builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us

builder - 10000 - elapsed: 3364us 
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us

Ne pas tenir compte des résultats pour 1 concaténation (JIT ne faisait pas encore son travail), même pour 10 concaténations la peine de performance est pertinente; pour des milliers de concaténations, la différence est énorme.

Les leçons tirées de cette expérience très rapide (facilement reproductible avec le code ci-dessus): ne jamais utiliser la += pour concaténer des chaînes de caractères, même dans les cas très simples où quelques concaténations sont nécessaires (comme on dit, la création de nouvelles chaînes est cher de toute façon et exerce une pression sur le GC).

Faire la méthode toString aussi lisible que possible!

La seule exception pour cela dans mon livre est si vous pouvez prouver pour moi qu'il consomme des ressources importantes :) (Oui, cela signifie profilage)

Notez également que le compilateur Java 5 génère un code plus rapide que l'approche « StringBuffer » manuscrite utilisé dans les versions antérieures de Java. Si vous utilisez « + » et cela vient des améliorations futures gratuitement.

Il semble y avoir un débat si l'utilisation StringBuilder est encore nécessaire avec les compilateurs actuels. Donc, je pensais que je vais donner mes 2 cents d'expérience.

J'ai un jeu de résultats JDBC de 10k dossiers (oui, je dois tous en un seul lot.) Utilisation de l'opérateur + prend environ 5 minutes sur ma machine avec Java 1.8. L'utilisation stringBuilder.append("") prend moins d'une seconde pour la même requête.

Donc, la différence est énorme. A l'intérieur d'une StringBuilder boucle est beaucoup plus rapide.

Voir l'exemple ci-dessous:

//java8
static void main(String[] args) {
    case1();//str.concat
    case2();//+=
    case3();//StringBuilder
}

static void case1() {
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str = str.concat(UUID.randomUUID()+"---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");
}

static void case2() {
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str+=UUID.randomUUID()+"---";
        saveTime(savedTimes, startTime);
    }        
    System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");
}

static void case3() {
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    StringBuilder str = new StringBuilder("");
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str.append(UUID.randomUUID()+"---");
        saveTime(savedTimes, startTime);
    }        
    System.out.println("Created string of length:"+str.length()+" in "+(System.currentTimeMillis()-startTimeAll)+" ms");

}

static void saveTime(List<Long> executionTimes, long startTime) {
        executionTimes.add(System.currentTimeMillis()-startTime);
        if(executionTimes.size()%CALC_AVG_EVERY == 0) {
            out.println("average time for "+executionTimes.size()+" concatenations: "+
                    NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(()->0))+
                    " ms avg");
            executionTimes.clear();
        }
}

Sortie:

  

temps moyen pour 10000 concaténations: 0,096 ms avg
  durée moyenne de 10000 concaténations: 0,185 ms avg
  durée moyenne de 10000 concaténations: 0,327 ms avg
  durée moyenne de 10000 concaténations: 0,501 ms avg
  durée moyenne de 10000 concaténations: 0,656 ms avg
  chaîne créée de longueur: 1950000 17745 ms   durée moyenne de 10000 concaténations: 0,21 ms avg
  durée moyenne de 10000 concaténations: 0,652 ms avg
  durée moyenne de 10000 concaténations: 1,129 ms avg
  durée moyenne de 10000 concaténations: 1,727 ms avg
  durée moyenne de 10000 concaténations: 2,302 ms avg
  chaîne créée de longueur: 1950000 60279 ms   durée moyenne de 10000 concaténations: 0,002 ms avg
  durée moyenne de 10000 concaténations: 0,002 ms avg
  durée moyenne de 10000 concaténations: 0,002 ms avg
  durée moyenne de 10000 concaténations: 0,002 ms avg
  durée moyenne de 10000 concaténations: 0,002 ms avg
  chaîne créée de longueur: 1950000 100 ms

Comme la longueur de chaîne augmente, le temps de concaténation. C'est là le StringBuilder est sans aucun doute nécessaire.
Comme vous le voyez, la concaténation. UUID.randomUUID()+"---", ne touche pas vraiment le temps

PS: Je ne pense pas que Quand utiliser StringBuilder en Java est vraiment un double de cela.
Cette question parle toString() qui la plupart du temps ne fonctionne pas concaténations de chaînes énormes.

Performance sage concaténation String à l'aide « + » est plus coûteux, car il doit faire une toute nouvelle copie de la chaîne depuis Strings sont immuables en Java. Celui-ci joue le rôle particulier si concaténation est très fréquent, par exemple: dans une boucle. Voici ce que mon idée suggère lorsque je tente de faire une telle chose:

Règles générales:

  • Au sein d'une seule affectation de chaîne, en utilisant la concaténation chaîne est très bien.
  • Si vous en boucle pour construire un grand bloc de données de caractère, allez StringBuffer.
  • Utilisation + = sur une chaîne va toujours être moins efficace que d'utiliser un StringBuffer, il devrait donc sonner les cloches d'avertissement - mais dans certains cas, l'optimisation acquise sera négligeable par rapport aux problèmes de lisibilité, utilisez donc votre bon sens.

Voici un joli blog de Jon Skeet autour de ce sujet.

Puis-je souligner que si vous allez itérer sur une collection et utiliser StringBuilder, vous pouvez consulter Apache Commons Lang et StringUtils.join () (en différentes saveurs)?

Quelle que soit la performance, il vous évitera de devoir créer StringBuilders et pour les boucles pour ce qui semble être le millionième temps.

Je comparais quatre approche différente pour comparer les performances. Je ne sais pas exactement ce qui se passe à gc, mais la chose importante pour moi est le temps. Compilateur est un facteur important here.I utilisé jdk1.8.0_45 sous plate-forme window8.1.

concatWithPlusOperator = 8
concatWithBuilder = 130
concatWithConcat = 127
concatStringFormat = 3737
concatWithBuilder2 = 46

public class StringConcatenationBenchmark {

private static final int MAX_LOOP_COUNT = 1000000;

public static void main(String[] args) {

    int loopCount = 0;
    long t1 = System.currentTimeMillis();
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithPlusOperator();
        loopCount++;
    }
    long t2 = System.currentTimeMillis();
    System.out.println("concatWithPlusOperator = " + (t2 - t1));

    long t3 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithBuilder();
        loopCount++;
    }
    long t4 = System.currentTimeMillis();
    System.out.println("concatWithBuilder = " + (t4 - t3));

    long t5 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithConcat();
        loopCount++;
    }
    long t6 = System.currentTimeMillis();
    System.out.println("concatWithConcat = " + (t6 - t5));

    long t7 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatStringFormat();
        loopCount++;
    }
    long t8 = System.currentTimeMillis();
    System.out.println("concatStringFormat = " + (t8 - t7));

    long t9 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithBuilder2();
        loopCount++;
    }
    long t10 = System.currentTimeMillis();
    System.out.println("concatWithBuilder2 = " + (t10 - t9));
}

private static void concatStringFormat() {
    String s = String.format("%s %s %s %s ", "String", "String", "String", "String");
}

private static void concatWithConcat() {
    String s = "String".concat("String").concat("String").concat("String");
}

private static void concatWithBuilder() {
    StringBuilder builder=new StringBuilder("String");
    builder.append("String").append("String").append("String");
    String s = builder.toString();
}

private static void concatWithBuilder2() {
    String s = new StringBuilder("String").append("String").append("String").append("String").toString();
}

private static void concatWithPlusOperator() {
    String s = "String" + "String" + "String" + "String";
}
}

Voici ce que je suis arrivé Java8

  • Utilisation concaténation chaîne
  • Utilisation StringBuilder

    long time1 = System.currentTimeMillis();
    usingStringConcatenation(100000);
    System.out.println("usingStringConcatenation " + (System.currentTimeMillis() - time1) + " ms");
    
    time1 = System.currentTimeMillis();
    usingStringBuilder(100000);
    System.out.println("usingStringBuilder " + (System.currentTimeMillis() - time1) + " ms");
    
    
    private static void usingStringBuilder(int n)
    {
        StringBuilder str = new StringBuilder();
        for(int i=0;i<n;i++)
            str.append("myBigString");    
    }
    
    private static void usingStringConcatenation(int n)
    {
        String str = "";
        for(int i=0;i<n;i++)
            str+="myBigString";
    }
    
  

Il est vraiment un cauchemar si vous utilisez la concaténation de chaîne pour un grand nombre de chaînes.

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms

Je pense que nous devrions aller avec StringBuilder append approche. La raison est

  1. La concaténation de chaîne va créer une nouvelle chaîne de caractères objet à chaque fois (As String est immuable objet), de sorte qu'il va créer des objets 3.

  2. Avec le constructeur chaîne un seul objet sera créé [StringBuilder est muttable] et la chaîne plus se sont ajoutées.

Pour les chaînes simples comme que je préfère utiliser

"string".concat("string").concat("string");

, je dirais que la méthode préférée de la construction d'une chaîne utilise StringBuilder, String # concat (), la surcharge opérateur +. StringBuilder est une augmentation significative des performances lors de grandes chaînes travaille comme utilisant l'opérateur + est une diminution importante de la performance (de façon exponentielle diminution importante que l'augmentation de la taille de la chaîne). Le seul problème avec l'utilisation .concat () est qu'il peut jeter NullPointerExceptions.

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