Étant donné les implémentations 2 toString()
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 ne fera peut-être pas une différence, mais à quel moment changeriez-vous de +
concat en StringBuilder
?
La version 1 est préférable car elle est plus courte et le compilateur la transformera en version 2 - aucune différence de performance.
Plus important encore, étant donné que nous n’avons que 3 propriétés, cela ne fera peut-être pas une différence, mais à quel moment passez-vous de concat en constructeur?
Au moment où vous êtes en train de concaténer une boucle, c'est généralement lorsque le compilateur ne peut pas remplacer StringBuilder
par lui-même.
La clé est de savoir si vous écrivez une seule concaténation au même endroit ou si vous l’accumulez avec le temps.
Pour l'exemple que vous avez donné, il est inutile 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, codez comme ceci:
...
String result = "";
for (String s : hugeArray) {
result = result + s;
}
est une perte de temps et de mémoire comparée à:
...
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 lisible.
Je n'optais pas pour la vitesse, sauf si vous l'utilisiez dans une boucle avec un nombre de répétitions très élevé et ont mesuré la différence de performance.
Je conviens que si vous devez générer beaucoup de paramètres, ce formulaire peut être déroutant (comme le dit l’un des commentaires). Dans ce cas, je passerais à une forme plus lisible (peut-être en utilisant ToStringBuilder de Apache-commons - tiré de la réponse de matt b) et en ignorant à nouveau les performances.
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 du pire 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:
slow elapsed 11741 ms
fast elapsed 7 ms
Le problème est que + + ajouter à une chaîne reconstruit une nouvelle chaîne, de sorte qu'il en coûte quelque chose de linéaire pour la longueur de vos chaînes (somme des deux).
Donc - à votre question:
La deuxième 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.
Je me suis également heurté à mon patron sur le fait d'utiliser append ou +. Comme ils utilisent Append (je n'arrive toujours pas à comprendre ce qu'ils disent chaque fois qu'un nouvel objet est créé). J'ai donc pensé à faire de la recherche et du développement. Bien que j'aime l'explication de Michael Borgwardt, je voulais simplement 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
; ---------------------------------------------------------------------------
Parmi les deux codes ci-dessus, vous pouvez constater que Michael a raison. Dans chaque cas, un seul objet SB est créé.
Depuis Java 1.5, une concaténation simple en une ligne avec "+" et StringBuilder.append () génèrent exactement le même bytecode.
Donc, dans un souci de lisibilité du code, utilisez "+".
2 exceptions:
En utilisant la dernière version de Java (1.8), le désassemblage (javap -c
) montre l'optimisation introduite par le compilateur. +
ainsi que sb.append()
générera un code très similaire. Cependant, il sera utile de vérifier le comportement si nous utilisons +
dans une boucle for.
Ajout de 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 la 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
Ajout de 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 la 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 si. Dans le premier cas, où +
a été utilisé, un nouveau StringBuilder
est créé pour chaque itération de boucle for et le résultat généré est enregistré en effectuant un appel toString()
(29 à 41). Vous générez donc des chaînes intermédiaires dont vous n’avez vraiment pas besoin lorsque vous utilisez l’opérateur +
dans la boucle for
.
Dans 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-28 :
L'idée est de remplacer la danse entière de StringBuilder par un simple appel invoqué dynamique à Java.lang.invoke.StringConcatFactory, qui acceptera les valeurs nécessitant une concaténation.
Pour des raisons de performances, l'utilisation de la concaténation +=
(String
) est déconseillée. La raison en est: Java String
est immuable, chaque fois qu'une nouvelle concaténation est effectuée, une nouvelle String
est créée (la nouvelle empreinte digitale est déjà différente de celle de l'ancienne). dans le pool String ). La création de nouvelles chaînes met la pression sur le CPG et ralentit le programme: la création d'objets coûte cher.
Le code ci-dessous devrait le rendre plus pratique et plus 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'une analyse sont indiqué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
Sans tenir compte des 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 reproductible avec le code ci-dessus): n'utilisez jamais le +=
pour concaténer des chaînes, même dans les cas très élémentaires où quelques concaténations sont nécessaires (comme dit, créer de nouvelles chaînes coûte quand même cher et met la pression sur le GC).
Apache Commons-Lang a une classe ToStringBuilder qui est super facile à utiliser. Il fait un bon travail en gérant à la fois la logique d’ajout et la mise en forme de ce que vous souhaitez pour votre toString.
public void toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("a", a);
tsb.append("b", b)
return tsb.toString();
}
Renverra une sortie qui ressemble à 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 souhaitez 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 de ToString si vous le souhaitez.
Rendez la méthode toString aussi lisible que possible!
La seule exception à cette règle dans mon livre est si vous pouvez me prouver qu'il consomme des ressources importantes :) (Oui, cela signifie le profilage)
Notez également que le compilateur Java 5 génère un code plus rapide que l'approche manuscrite "StringBuffer" utilisée dans les versions antérieures de Java. Si vous utilisez "+" ceci et les améliorations futures sont gratuits.
Il semble y avoir un débat quant à savoir si l'utilisation de StringBuilder est toujours nécessaire avec les compilateurs actuels. Alors j'ai pensé donner mon 2 centimes d'expérience.
J'ai _ un ensemble de résultats JDBC
de 10 000 enregistrements (oui, il me faut tous les éléments d'un lot.) L'utilisation de l'opérateur + prend environ 5 minutes sur ma machine avec Java 1.8
. L'utilisation de 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.
En termes de performances, la concaténation de chaînes utilisant '+' est plus coûteuse, car elle doit créer une nouvelle copie de String car les chaînes sont immuables en Java. Cela joue un rôle particulier si la concaténation est très fréquente, par exemple: dans une boucle. Voici ce que mon IDEA suggère lorsque je tente de faire une chose pareille:
Règles générales:
Voici un blog de Nice Jon Skeet autour de ce sujet.
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 10 000 concaténations: 0,096 ms en moyenne
temps moyen pour 10 000 concaténations: 0,185 ms en moyenne
temps moyen pour 10 000 concaténations: 0,327 ms en moyenne
temps moyen pour 10 000 concaténations: 0,501 ms en moyenne
temps moyen pour 10 000 concaténations: 0,656 ms en moyenne
Chaîne créée de longueur: 1950000 en 17745 ms
temps moyen pour 10 000 concaténations: 0,21 ms en moyenne
temps moyen pour 10 000 concaténations: 0,652 ms
temps moyen pour 10 000 concaténations: 1,129 ms
temps moyen pour 10 000 concaténations: 1,727 ms moyenne
temps moyen pour 10 000 concaténations: 2,302 ms
Chaîne de longueur créée: 1950000 en 60279 ms
temps moyen pour 10 000 concaténations: 0,002 ms
temps moyen pour 10 000 concaténations: 0,002 ms
temps moyen pour 10 000 concaténations: 0,002 ms
temps moyen pour 10 000 concaténations: 0,002 ms
temps moyen pour 10 000 concaténations: 0,002 ms
Chaîne créée de longueur: 1950000 en 100 ms
Lorsque la longueur de la chaîne augmente, le temps de la concaténation augmente également.
C’est là que la 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 que quand utiliser StringBuilder en Java est vraiment une copie de ceci.
Cette question parle de toString()
dont la plupart du temps n’effectue pas de concaténation d’énormes chaînes.
Puis-je préciser que si vous allez parcourir une collection et utiliser StringBuilder, vous pouvez vouloir vérifier Apache Commons Lang et StringUtils.join () (in différentes saveurs)?
Quelles que soient les performances, cela vous évitera de créer StringBuilders et des boucles for pour ce qui semble être le millionième temps.
J'ai comparé quatre approches différentes pour comparer les performances. Je ne sais pas exactement ce qui arrive à gc, mais l’important pour moi est le temps. Le compilateur est un facteur important ici. J'ai utilisé jdk1.8.0_45 sous la 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 j'ai vérifié en Java8
Utilisation de 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";
}
C'est vraiment un cauchemar si vous utilisez la concaténation de chaînes pour un grand nombre de chaînes.
usingStringConcatenation 29321 ms
usingStringBuilder 2 ms