Wenn Sie Folgendes kombinieren wörtlich Strings (wörtlich "foo" + "bar"
), macht der Compiler dies zur Kompilierzeit, nicht zur Laufzeit.
Wenn Sie zwei nicht-literalische Zeichenketten haben und sie mit +
verwendet der Compiler (jedenfalls der von Sun) eine StringBuilder
unter der Decke, aber nicht unbedingt auf die effizienteste Weise. Wenn Sie also zum Beispiel dies haben:
String repeat(String a, int count) {
String rv;
if (count <= 0) {
return "";
}
rv = a;
while (--count > 0) {
rv += a;
}
return rv;
}
...was der Sun-Compiler tatsächlich als Bytecode erzeugt, sieht so aus etwas wie diese:
String repeat(String a, int count) {
String rv;
if (count <= 0) {
return "";
}
rv = a;
while (--count > 0) {
rv = new StringBuilder().append(rv).append(a).toString();
}
return rv;
}
(Ja, wirklich - siehe die Disassemblierung am Ende dieser Antwort.) Beachten Sie, dass es eine neue StringBuilder
bei jeder Iteration, und konvertierte das Ergebnis dann in String
. Das ist ineffizient (aber das macht nichts, wenn man es nicht mit einem Los ) wegen der vielen temporären Speicherzuweisungen: Es wird ein StringBuilder
und dessen Puffer, ordnet den Puffer möglicherweise beim ersten Mal neu zu append
[wenn rv
mehr als 16 Zeichen lang ist, was die Standardpuffergröße ist] und wenn nicht auf der ersten, dann fast sicher auf der zweiten append
und weist dann eine String
am Ende - und tut es dann alle wieder bei der nächsten Iteration.
Sie könnten die Effizienz steigern, indem Sie sie gegebenenfalls so umschreiben, dass sie explizit eine StringBuilder
:
String repeat(String a, int count) {
StringBuilder rv;
if (count <= 0) {
return "";
}
rv = new StringBuilder(a.length() * count);
while (count-- > 0) {
rv.append(a);
}
return rv.toString();
}
Dort haben wir eine explizite StringBuilder
und auch die anfängliche Pufferkapazität so einstellen, dass sie groß genug ist, um das Ergebnis zu speichern. Das ist speichereffizienter, aber natürlich für unerfahrene Codeverwalter etwas unübersichtlicher und etwas mühsamer zu schreiben. Also wenn Sie ein Leistungsproblem mit einer engen String-Concat-Schleife feststellen, könnte dies eine Möglichkeit sein, dieses Problem zu beheben.
Sie können dies unter der Abdeckung sehen StringBuilder
in Aktion mit der folgenden Testklasse:
public class SBTest
{
public static final void main(String[] params)
{
System.out.println(new SBTest().repeat("testing ", 4));
System.exit(0);
}
String repeat(String a, int count) {
String rv;
if (count <= 0) {
return "";
}
rv = a;
while (--count > 0) {
rv += a;
}
return rv;
}
}
...der (mit Hilfe von javap -c SBTest
) wie folgt:
Compiled from "SBTest.java"
public class SBTest extends java.lang.Object{
public SBTest();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static final void main(java.lang.String[]);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: new #3; //class SBTest
6: dup
7: invokespecial #4; //Method "<init>":()V
10: ldc #5; //String testing
12: iconst_4
13: invokevirtual #6; //Method repeat:(Ljava/lang/String;I)Ljava/lang/String;
16: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
19: iconst_0
20: invokestatic #8; //Method java/lang/System.exit:(I)V
23: return
java.lang.String repeat(java.lang.String, int);
Code:
0: iload_2
1: ifgt 7
4: ldc #9; //String
6: areturn
7: aload_1
8: astore_3
9: iinc 2, -1
12: iload_2
13: ifle 38
16: new #10; //class java/lang/StringBuilder
19: dup
20: invokespecial #11; //Method java/lang/StringBuilder."<init>":()V
23: aload_3
24: invokevirtual #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: aload_1
28: invokevirtual #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: invokevirtual #13; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: astore_3
35: goto 9
38: aload_3
39: areturn
}
Beachten Sie, wie eine neue StringBuilder
wird erstellt am jede Iteration der Schleife und unter Verwendung der Standardpufferkapazität erstellt.
All diese temporären Zuweisungen klingen hässlich, aber auch das nur, wenn man es mit umfangreichen Schleifen und/oder umfangreichen Strings zu tun hat. Wenn der resultierende Bytecode ausgeführt wird, kann die JVM ihn außerdem weiter optimieren. Die HotSpot JVM von Sun beispielsweise ist ein sehr ausgereifter JIT-optimierender Compiler. Sobald er die Schleife als Hot Spot identifiziert hat, kann er durchaus einen Weg finden, sie zu refaktorisieren. Oder natürlich auch nicht :-)
Meine Faustregel ist, dass ich mich darum kümmere, wenn ich ein Leistungsproblem sehe, oder wenn ich weiß, dass ich eine Los der Verkettung und es ist sehr wahrscheinlich ein Leistungsproblem zu sein, und der Code wird vom Standpunkt der Wartungsfreundlichkeit nicht wesentlich beeinträchtigt, wenn ich eine StringBuilder
stattdessen. Die wütende Liga der Optimierungsgegner würde mir bei der zweiten Frage wahrscheinlich widersprechen :-)
1 Stimmen
Ich empfehle Ihnen, einen Blick auf diese Seite zu werfen hervorragender Artikel .