T.J*_*der 36
如果组合文字字符串(字面意思"foo" + "bar"),编译器会在编译时完成,而不是在运行时.
如果你有两个非文字字符串并加入它们+,编译器(无论如何,Sun)将使用一个StringBuilder封面,但不一定以最有效的方式.例如,如果你有这个:
String repeat(String a, int count) {
String rv;
if (count <= 0) {
return "";
}
rv = a;
while (--count > 0) {
rv += a;
}
return rv;
}
Run Code Online (Sandbox Code Playgroud)
......什么Sun编译器实际上会产生字节码看起来事情是这样的:
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;
}
Run Code Online (Sandbox Code Playgroud)
(是的,真的 - 请参阅本答案末尾的反汇编.)请注意,它StringBuilder在每次迭代时都创建了一个新的,然后将结果转换为String.由于所有的临时内存分配,这是低效的(但除非你做了很多事情并不重要):它分配一个StringBuilder及其缓冲区,很可能在第一个上重新分配缓冲区append[如果rv超过16个字符long,这是默认的缓冲区大小]如果不在第一个,那么几乎肯定在第二个append,然后String在最后分配一个- 然后在下一次迭代再次完成.
如有必要,您可以通过重写它来明确使用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();
}
Run Code Online (Sandbox Code Playgroud)
在那里,我们使用了显式StringBuilder,并将其初始缓冲区容量设置得足够大,以保存结果.这样的内存效率更高,但当然,对于没有经验的代码维护者来说,稍微不那么清楚,而且编写的痛苦也就多了.因此,如果您发现紧密字符串连接循环的性能问题,这可能是解决它的方法.
您可以StringBuilder使用以下测试类来查看此操作:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
......这样拆解(使用javap -c SBTest):
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
}
Run Code Online (Sandbox Code Playgroud)
请注意如何在循环的每次迭代中StringBuilder创建new 并使用默认缓冲区容量创建.
所有这些临时分配的东西听起来很丑陋,但同样,只有当你处理大量循环和/或大量字符串时.此外,当运行生成的字节码时,JVM可以进一步优化它.例如,Sun的HotSpot JVM是一个非常成熟的JIT优化编译器.一旦它将循环识别为热点,它就可能找到重构它的方法.或者当然不是.:-)
我的经验法则是当我看到性能问题时,我担心它,或者我知道我正在进行大量连接,并且很可能是性能问题,并且从可维护性的角度来看,代码不会受到太大影响如果我用一个StringBuilder代替.狂热的反早熟优化联盟可能会在第二种情况下与我不同意.:-)
Pab*_*ruz 14
不,这是没有使用相同的
StringBuilder比做"a" + "b".
在Java中,String实例是不可变的.
所以,如果你这样做:
String c = "a" + "b";
Run Code Online (Sandbox Code Playgroud)
每次连接时都会创建新的字符串.
另一方面,StringBuilder就像一个缓冲区,可以在追加新的字符串时增长.
StringBuilder c = new StringBuilder();
c.append("a");
c.append("b"); // c is only created once and appended "a" and "b".
Run Code Online (Sandbox Code Playgroud)
经验法则(由于我得到的评论而改变):
如果要连接很多(例如,在循环内连接,或生成由多个字符串连接变量组成的大型XML),请使用StringBuilder.否则,简单的连接(使用+运算符)就可以了.
编译这种代码时,编译器优化也起着重要作用.
有关此问题的更多StackOVerflow问题:
StringBuilder vs Java中toString()的字符串连接
| 归档时间: |
|
| 查看次数: |
23897 次 |
| 最近记录: |