Question StringBuilder vs String concaténation dans toString () en Java


Étant donné le 2 toString() implémentations ci-dessous, lequel 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 3 propriétés, cela pourrait ne pas faire de différence, mais à quel moment passeriez-vous de + concat à StringBuilder?


766
2017-10-07 15:44


origine


Réponses:


La version 1 est préférable car elle est plus courte et le compilateur va en fait le transformer en version 2 - aucune différence de performance.

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

Au point où vous êtes en train de concaténer une boucle - c'est généralement quand le compilateur ne peut pas se substituer StringBuilder par lui-même.


804
2017-10-07 15:51



La clé est de savoir si vous écrivez une seule concaténation au même endroit ou si vous l'accumulez au fil du temps.

Pour l'exemple que vous avez donné, il ne sert à rien d'utiliser explicitement StringBuilder. (Regardez le code compilé pour votre premier cas.)

Mais si vous construisez une chaîne par exemple dans une boucle, utilisez StringBuilder.

Pour clarifier, en supposant que hugeArray contient des milliers de chaînes, 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();

204
2017-10-07 15:47



Je préfère:

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

... parce que c'est court et lisible.

je voudrais ne pas optimiser ceci pour la vitesse, sauf si vous l'utilisez dans une boucle avec un nombre de répétitions très élevé et ont mesuré la différence de performance.

Je suis d'accord, que si vous devez sortir beaucoup de paramètres, cette forme peut devenir confuse (comme l'un des commentaires disent). Dans ce cas, je passerais à une forme plus lisible (peut-être en utilisant ToStringBuilder d'apache-commons - pris de la réponse de matt b) et ignorer la performance à nouveau.


64
2017-10-07 15:47



Dans la plupart des cas, vous ne verrez pas de différence réelle entre les deux approches, mais il est facile de construire un scénario catastrophe 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:

slow elapsed 11741 ms
fast elapsed 7 ms

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

Donc - à votre question:

La seconde approche serait plus rapide, mais moins lisible et plus difficile à maintenir. Comme je l'ai dit, dans votre cas particulier, vous ne verriez probablement pas la différence.


56
2017-10-07 15:58



J'ai aussi eu des conflits avec mon patron sur le fait d'utiliser append ou +. Comme ils utilisent Append (je n'arrive toujours pas à comprendre comme ils disent chaque fois qu'un nouvel objet est créé). J'ai donc pensé faire de la R & D. Bien que j'adore l'explication de Michael Borgwardt, je voulais juste montrer une explication si quelqu'un aurait vraiment besoin de savoir à 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 sort 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
; ---------------------------------------------------------------------------

À partir des deux codes ci-dessus, vous pouvez voir que Michael a raison. Dans chaque cas, un seul objet SB est créé.


26
2017-12-21 20:29



Depuis Java 1.5, une simple concaténation d'une ligne avec "+" et StringBuilder.append () génère 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

23
2018-04-16 14:14



En utilisant la dernière version de Java (1.8) le démontage (javap -c) montre l'optimisation introduite par le compilateur. + ainsi que sb.append() va générer du code très similaire. Cependant, il vaudra la peine d'inspecter le comportement si nous utilisons + dans une boucle for.

Ajouter des chaînes en utilisant + dans une boucle for

Java:

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

ByteCode :(for extrait de boucle)

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

Ajouter des chaînes en utilisant stringbuilder.append

Java:

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

ByteCdoe :(for extrait de boucle)

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 bien que. Dans le premier cas, où + a été utilisé, nouveau StringBuilder est créé pour chaque pour l'itération de la boucle et le résultat généré est stocké en faisant un toString() appel (29 à 41). Vous générez donc des chaînes intermédiaires dont vous n'avez vraiment pas besoin lorsque vous utilisez + opérateur dans for boucle.


19
2018-05-26 07:15