StringBuilder vs concaténation de Chaîne dans toString() en Java
étant donné les 2 toString()
implémentations ci-dessous, laquelle est préférée:
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 n'avons que 3 propriétés, cela pourrait ne pas faire de différence, mais à quel moment passer de +
concat à StringBuilder
?
18 réponses
Version 1 est préférable car il est plus court et le compilateur va en fait le transformer en version 2 - aucune différence de performance que ce soit.
plus important étant donné que nous n'avons que 3 propriétés il pourrait ne pas faire différence, mais à quel point pensez-vous passer de concat à builder?
au point où vous concaténez dans une boucle - c'est généralement quand le compilateur ne peut pas remplacer StringBuilder
par lui-même.
la clé est de savoir si vous écrivez une seule concaténation tout en un seul endroit ou si vous l'accumulez avec le temps.
pour l'exemple que vous avez donné, il n'y a pas de raison d'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, en supposant que hugeArray contient des milliers de chaînes, code comme ceci:
...
String result = "";
for (String s : hugeArray) {
result = result + s;
}
est un 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 que c'est court et facile à lire.
je pas optimiser ce pour la vitesse, sauf si vous l'utilisez à l'intérieur d'une boucle avec un très grand nombre de répétitions et ont mesuré la différence de performances.
je suis d'accord, que si vous devez sortir beaucoup de paramètres, ce formulaire peut devenir confus (comme un des commentaires disent). Dans ce cas, j'aimerais passer à un forme plus lisible (peut-être en utilisant ToStringBuilder d'apache - commons-tiré de la réponse de matt b) et ignorer la performance à nouveau.
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 pire scénario 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 To += ajouter à une chaîne reconstruit une nouvelle chaîne, de sorte qu'elle coûte quelque chose de linéaire à la longueur de vos chaînes (somme des deux).
Donc, à votre question:
la deuxième approche serait plus rapide, mais c'est moins lisible et plus difficile à maintenir. Comme je l'ai dit, dans votre cas particulier, vous ne verriez probablement pas la différence.
j'ai également eu des conflits avec mon patron sur le fait d'utiliser l'ajout ou +.Comme ils utilisent L'ajout (Je ne peux toujours pas comprendre comme ils disent chaque fois qu'un nouvel objet est créé). Donc j'ai pensé à faire un peu de R&D. Bien que J'aime Michael Borgwartt explication mais je voulais juste montrer une explication si quelqu'un aura 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 apparaît 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
; ---------------------------------------------------------------------------
des deux codes ci-dessus Michael a raison.Dans chaque cas, un seul objet SB est créé.
depuis Java 1.5, concaténation simple d'une ligne avec "+" et StringBuilder.ajoute () génère exactement le même bytecode.
ainsi, pour des raisons de lisibilité du code, utilisez"+".
2 exceptions:
- environnement multithread : StringBuffer
- concaténation en boucles: StringBuilder / StringBuffer
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()
générera du code très similaire. Cependant, il sera intéressant d'examiner le comportement si nous utilisons +
dans une boucle for.
ajouter des 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: ( 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 cordes en utilisant stringbuilder.annexe
Java:
public String myCatSb(String[] vals) {
StringBuilder sb = new StringBuilder();
for(String val : vals) {
sb.append(val);
}
return sb.toString();
}
ByteCdoe:( for
boucle extrait)
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 ya un peu de différence flagrante cependant. Dans le 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é par faire un appel toString()
(29 à 41). Vous générez donc des chaînes intermédiaires dont vous n'avez vraiment pas besoin en utilisant l'opérateur +
dans la boucle for
.
Apache Commons-Lang a une classe ToStringBuilder qui est super facile à utiliser. Il fait un bon travail à la fois la manipulation de l'append-logique ainsi que le formatage de la façon dont vous voulez que votre toString regarder.
public void toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("a", a);
tsb.append("b", b)
return tsb.toString();
}
renvoie une sortie ressemblant à com.blah.YourClass@abc1321f[a=whatever, b=foo]
.
ou sous une forme plus condensée utilisant le chaînage:
public void toString() {
return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}
Ou si vous voulez utiliser la réflexion pour inclure tous les champs de la classe:
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
vous pouvez également personnaliser le style du ToString si vous voulez.
pour des raisons de performance, l'utilisation de +=
( String
concaténation) est déconseillée. La raison en est: Java String
est un immuable, chaque fois qu'une nouvelle concaténation est faite un nouveau String
est créé (le nouveau a une empreinte différente de l'ancienne déjà dans le pool de cordes ). La création de nouvelles chaînes de caractères exerce une pression sur le GC et ralentit le programme: la création d'objets coûte cher.
sous le code doit 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 d'un essai 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 considérant pas les résultats pour 1 concaténation (JIT ne faisait pas encore son travail), même pour 10 concaténations la pénalité de performance est pertinente; pour des milliers de concaténations, la différence est énorme.
leçons tirées de cette expérience très rapide (facilement reproductibles avec les données ci-dessus) code): n'utilisez jamais le +=
pour concaténer les chaînes ensemble, même dans les cas très basiques où quelques concaténations sont nécessaires (comme dit, créer de nouvelles chaînes est cher de toute façon et met la pression sur le GC).
en Java 9 la version 1 devrait être plus rapide car elle est convertie en appel invokedynamic
. Plus de détails peuvent être trouvés dans JEP-280 :
l'idée est de remplacer tout L'ajout StringBuilder par un simple appel invokedynamique à java.lang.invoquer.StringConcatFactory, qui permettra d'accepter les valeurs de la nécessité de la concaténation.
rendre la méthode toString aussi lisible que vous le pouvez!
la seule exception pour cela dans mon livre est si vous pouvez prouver à 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ée dans les versions précédentes de Java. Si vous utilisez " + " cette option et les améliorations futures sont gratuites.
il semble y avoir un débat pour savoir si L'utilisation de StringBuilder est toujours nécessaire avec les compilateurs actuels. Alors j'ai pensé que je donnerais mes 2 cents d'expérience.
j'ai un jeu de résultats JDBC
d'enregistrements 10k (Oui, j'ai besoin de tous les enregistrements dans un lot.) L'utilisation de l'opérateur + prend environ 5 minutes sur ma machine avec Java 1.8
. Utiliser stringBuilder.append("")
prend moins d'une seconde pour la même requête.
Donc, la différence est énorme. L'intérieur d'une boucle StringBuilder
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 mme avg
temps moyen pour 10000 concaténations: 0,185 ms moyenne
temps moyen pour 10000 concaténations: 0,327 ms avg
temps moyen pour 10000 concaténations: 0.501 ms avg
temps moyen pour 10000 concaténations: De 0,656 mme avg
Créé chaîne de longueur: 1950000 dans 17745 ms
temps moyen pour 10000 concaténations: 0,21 ms avg
temps moyen pour 10000 concaténations: 0.652 ms avg
temps moyen pour 10000 concaténations: 1,129 ms avg
temps moyen pour 10000 concaténations: 1,727 ms avg
temps moyen pour 10000 concaténations: 2.302 mme avg
Chaîne de longueur créée: 1950000 en 60279 ms "1519210920
temps moyen pour 10000 concaténations: 0,002 ms avg
temps moyen pour 10000 concaténations: 0,002 ms avg
temps moyen pour 10000 concaténations: 0,002 ms avg
temps moyen pour 10000 concaténations: 0,002 ms avg
moyen temps pour 10000 concaténations: 0,002 ms avg
Chaîne de longueur créée: 1950000 in 100 ms
Plus La longueur de la chaîne augmente, plus le temps de concaténation augmente.
C'est là que le StringBuilder
est absolument nécessaire.
Comme vous le voyez, la concaténation: UUID.randomUUID()+"---"
, n'affecte pas vraiment le temps.
P.S.: Je ne pense pas quand utiliser StringBuilder en Java est vraiment une copie de ceci.
Cette question parle de toString()
qui, la plupart du temps, n'exécute pas de concaténations de cordes énormes.
puis-je faire remarquer que si vous allez itérer sur une collection et utiliser StringBuilder, vous pouvez vérifier Apache Commons Lang et StringUtils.joindre () (dans des saveurs différentes) ?
quelle que soit la performance, cela vous évitera d'avoir à créer des StringBuilders et des boucles pour ce qui semble être le millionième temps.
j'ai comparé quatre approches différentes pour comparer la performance. Je ne sais pas exactement ce qui arrive à gc, mais ce qui compte pour moi, c'est le temps. Le compilateur est un facteur important.J'ai utilisé jdk1.8.0_45 sous window8.1 plate-forme.
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";
}
}
la concaténation de la chaîne de caractères '+' est plus coûteuse car elle doit faire une toute nouvelle copie de la chaîne de caractères puisque les chaînes de caractères sont immuables en java. Cela joue un rôle particulier si la concaténation est très fréquente, par exemple: à l'intérieur d'une boucle. Voici ce que mon idée suggère quand je tente de faire une telle chose:
Règles Générales:
- dans une seule chaîne assignation, en utilisant la concaténation de chaîne de caractères est très bien.
- si vous êtes en boucle pour construire un grand bloc de données de caractères, allez pour StringBuffer.
- utiliser += sur une chaîne va toujours être moins efficace que d'utiliser un StringBuffer, donc il devrait sonner les cloches d'avertissement - mais dans certains cas, l'optimisation gagnée sera négligeable par rapport aux problèmes de lisibilité, alors utilisez votre bon sens.
voici un nice Jon Skeet blog autour de ce sujet.
pour des cordes simples comme ça je préfère utiliser
"string".concat("string").concat("string");
dans l'ordre, je dirais que la méthode préférée pour construire une chaîne est D'utiliser StringBuilder, String#concat(), puis l'opérateur + surchargé. StringBuilder est une augmentation significative de la performance lorsque le travail de grandes cordes tout comme l'utilisation de l'opérateur + est une diminution importante de la performance (diminution exponentielle grande que la taille de la corde augmente). Le seul problème avec l'aide de .concat (), c'est qu'il peut jeter NullPointerExceptions.
je pense que nous devrions aller avec StringBuilder ajouter approche. La raison est
-
la chaîne concaténate créera un nouvel objet string à chaque fois (car la chaîne est un objet immuable) , donc elle créera 3 objets.
-
Avec String builder, un seul objet sera créé[StringBuilder est mutable] et la chaîne suivante sera ajoutée.