【发布时间】:2023-03-02 23:59:01
【问题描述】:
我读到了 Java 使用 += 运算符的方式,使用 StringBuilder。
跟("a" + "b")操作一样吗?
【问题讨论】:
-
我建议你看看这个excellent article。
我读到了 Java 使用 += 运算符的方式,使用 StringBuilder。
跟("a" + "b")操作一样吗?
【问题讨论】:
如果你结合 literal 字符串(字面意思是 "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;
}
...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;
}
(是的,真的 - 请参阅此答案末尾的反汇编。)请注意,它在每次迭代时创建一个新的 StringBuilder,然后将结果转换为 String。由于所有的临时内存分配,这是低效的(但除非你经常这样做,否则没关系):它分配一个 StringBuilder 及其缓冲区,很可能重新分配缓冲区在第一个append [如果rv 的长度超过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 并将其初始缓冲区容量设置为足够大以容纳结果。这更节省内存,但当然,对于没有经验的代码维护人员来说,这有点不太清楚,而且写起来也有点痛苦。所以如果你发现一个紧密的字符串连接循环的性能问题,这可能是解决它的一种方法。
您可以在下面的测试类中看到这个 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):
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
}
注意如何在循环的每次迭代中创建一个新的StringBuilder,并使用默认缓冲区容量创建。
所有这些临时分配的东西听起来都很丑陋,但同样,只有当您处理大量循环和/或大量字符串时。此外,当生成的字节码运行时,JVM 可能会进一步优化它。例如,Sun 的 HotSpot JVM 是一个非常成熟的 JIT 优化编译器。一旦将循环识别为热点,它很可能会找到重构它的方法。或者不是,当然。 :-)
我的经验法则是,当我看到性能问题时,我会担心它,或者如果我知道我正在执行 很多 连接并且它很可能是一个性能问题,如果我改用StringBuilder,从可维护性的角度来看,代码不会受到重大影响。狂热的反过早优化联盟可能会在其中的第二个问题上不同意我的看法。 :-)
【讨论】:
没有。使用
StringBuilder和使用"a" + "b"不一样。
在 Java 中,String 实例是不可变的。
所以,如果你这样做:
String c = "a" + "b";
每次连接时都会创建新的字符串。
另一方面,StringBuilder 就像一个缓冲区,可以在追加新字符串时根据需要增长。
StringBuilder c = new StringBuilder();
c.append("a");
c.append("b"); // c is only created once and appended "a" and "b".
经验法则是(由于我得到了 cmets 而改变):
如果您要进行大量连接(即在循环内进行连接,或生成由多个字符串连接变量组成的大型 XML),请使用 StringBuilder。否则,简单的连接(使用 + 运算符)就可以了。
编译器优化在编译此类代码时也发挥着重要作用。
Here's对该主题的进一步解释。
还有关于这个问题的更多 StackOVerflow 问题:
Is it better to reuse a StringBuilder in a loop?
What's the best way to build a string of delimited items in Java?
【讨论】:
stringA += stringB,请改用StringBuilder,因为它确实会吃掉堆。您的回答更暗示您需要一个StringBuilder 为每个stringA + stringB ,这不是真的。
String。经验法则应该是,如果您要在循环中创建String,并且它有可能循环多次,请使用StringBuilder。用StirngBuilder 使线性串联代码复杂化毫无意义。
是的,是一样的,但是编译器可以在发布代码之前额外优化literals的连接,所以"a"+"b"可以直接发布为"ab"。
【讨论】:
为了将固定数量的字符串在一个表达式中与+连接起来,编译器将使用单个StringBuilder生成代码。
例如线
String d = a + b + c;
产生与行相同的字节码
String d = new StringBuilder().append(a).append(b).append(c).toString();
使用 javac 编译器编译时。 (Eclipse 编译器通过调用new StringBuilder(a) 生成更优化的代码,从而节省了一个方法调用。)
正如其他答案中提到的,编译器会将像 "a" + "b" 这样的字符串文字连接成一个字符串本身,生成包含 "ab" 的字节码。
正如网上到处提到的,你不应该使用+ 来构建一个字符串在一个循环中,因为你将字符串的开头一遍又一遍地复制到新的字符串中。在这种情况下,您应该使用一个在循环外声明的StringBuilder。
【讨论】:
"a" + "b"操作
虽然可读、易于格式化和直接,但用“+”连接字符串在 Java 中被认为是不好的。
每次通过 '+' (String.concat()) 追加内容时,都会创建一个新字符串,复制旧字符串内容,追加新内容,并丢弃旧字符串。字符串越大,花费的时间就越长——要复制的内容越多,产生的垃圾就越多。 注意:如果您只是连接几个(比如 3,4)字符串,而不是通过循环构建字符串或只是编写一些测试应用程序,您仍然可以坚持使用“+”
使用
StringBuilder
在执行大量字符串操作(或通过循环追加)时,可能建议将“+”替换为StringBuilder.append。在append() 方法调用期间不会创建“+”中提到的中间对象。
还要注意 Sun Java 编译器中的优化,当它看到字符串连接时会自动创建 StringBuilders (StringBuffers
【讨论】:
字符串更常见的是连接 使用 + 运算符,如
"Hello," + " world" + "!"
【讨论】:
StringBuilder 隐藏起来。)