Java如何使用“+”来进行string连接?
我使用StringBuilder
阅读了关于Java使用+=
运算符的方式。
("a" + "b")
操作是否一样?
不。使用
StringBuilder
与使用"a" + "b"
。
在Java中,String实例是不可变的。
所以,如果你这样做:
String c = "a" + "b";
每次连接时都会创build新的string。
另一方面,StringBuilder就像一个缓冲区,可以在追加新的string时根据需要进行增长。
StringBuilder c = new StringBuilder(); c.append("a"); c.append("b"); // c is only created once and appended "a" and "b".
拇指规则(改变感谢我得到的意见):
如果要连接很多(即在一个循环内连接,或者生成由多个string连接的variables组成的大XML),请使用StringBuilder。 否则,简单的连接(使用+运算符)将会很好。
编译器优化在编译这种types的代码时也起着巨大的作用。
这里是关于这个话题的进一步解释。 更多关于这个问题的StackOVerflow问题:
一 , 二 , 三
如果将string(字面意思是"foo" + "bar"
)组合在一起,编译器会在编译时执行它,而不是在运行时。
如果你有两个非文字string并且用+
join,编译器(无论如何)都会使用一个StringBuilder
,但不一定是最有效的方式。 所以举个例子,如果你有这个:
String repeat(String a, int count) { String rv; if (count <= 0) { return ""; } rv = a; while (--count > 0) { rv += a; } return rv; }
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; }
(是的,真的 – 看到这个答案结束时的反汇编。)请注意,它在每次迭代中创build了一个新的StringBuilder
,然后将结果转换为String
。 由于所有的临时内存分配,这是效率低下的(但不要紧,除非你这么做):它分配一个StringBuilder
及其缓冲区,很可能在第一次append
重新分配缓冲区[if rv
more长度超过16个字符,这是默认的缓冲区大小],如果不是第一个,那么几乎肯定是第二个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(); }
在那里,我们使用了一个显式的StringBuilder
,并且将它的初始缓冲区容量设置得足够大以保存结果。 这样更有记忆效率,但是对于没有经验的代码维护者来说,这当然不那么清楚,而且写起来更麻烦。 所以, 如果你发现一个紧凑的stringconcat循环的性能问题,这可能是解决它的一种方法。
你可以在下面的testing类中看到这个在下面的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; } }
…这样反javap -c SBTest
(使用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 }
注意在循环的每次迭代中如何创build一个新的StringBuilder
,并使用默认的缓冲区容量创build。
所有这些临时分配的东西听起来都很难看,但是再一次,只有当你处理大量的循环和/或大量的string时。 而且,当生成的字节码运行时,JVM可能会进一步优化它。 Sun的HotSpot JVM,例如,是一个非常成熟的JIT优化编译器。 一旦确定循环为热点,就可能find重构它的方法。 或者不,当然。 🙂
我的经验法则是当我看到一个性能问题时,我担心它,或者如果我知道我正在进行大量的连接,这很可能是性能问题,并且从可维护性的angular度来看,代码不会受到显着的影响如果我使用StringBuilder
来代替。 那些反对过早优化的狂热分子可能会不赞同我的第二个。 🙂
是的,这是相同的,但编译器可以在发布代码之前优化文字连接,所以"a"+"b"
可以直接发布为"ab"
。
为了将一个expression式中固定数量的string连接到+
,编译器将使用一个StringBuilder
产生代码。
如线
String d = a + b + c;
导致与该行相同的字节码
String d = new StringBuilder().append(a).append(b).append(c).toString();
当使用javac编译器进行编译时。 (Eclipse编译器通过调用new StringBuilder(a)
生成更优化的代码,从而节省一个方法调用。)
正如其他答案中提到的那样,编译器会将string文字(如"a" + "b"
成一个string本身,生成包含"ab"
字节码。
正如在网上提到的那样,你不应该使用+
来在循环中build立一个string,因为你要将string的开始部分复制到新的string中。 在这种情况下,你应该使用一个你在循环之外声明的StringBuilder
。
"a" + "b"
操作
虽然可读性强,容易格式化和直接,但用“+”连接string在Java中被认为是不好的。
每次通过“+”(String.concat())追加一些东西时,都会创build一个新的string,旧的string内容被复制,新的内容被追加,旧的string被丢弃。 string得到的时间越长,复制的次数越多,产生的垃圾越多。 注意:如果你只是连接几个(比如3,4个)string,而不是通过循环创build一个string,或者只是写一些testing应用程序,你仍然可以坚持“+”
使用
StringBuilder
当执行大量的string操作(或通过循环追加)时,可能推荐使用StringBuilder
.appendreplace“+”。 在“+”的情况下提到的中间对象在append()
方法调用期间不会被创build。
另外需要注意的是,在Sun Java编译器中进行优化,当它看到string连接时会自动创buildStringBuilders
( StringBuffers
<5.0)。 但那只是Sun Java编译器。
string更常用+运算符连接,如
"Hello," + " world" + "!"
资源