Java中toString()中的StringBuilder与字符串连接

Java中toString()中的StringBuilder与字符串连接,java,performance,string,concatenation,stringbuilder,Java,Performance,String,Concatenation,Stringbuilder,给定下面的2个toString()实现,首选哪一个: public String toString(){ return "{a:"+ a + ", b:" + b + ", c: " + c +"}"; } 或 ? 更重要的是,考虑到我们只有3个属性,这可能没有什么区别,但在什么情况下,您会从+concat切换到StringBuilder?关键是您是在一个地方编写单个串联,还是随着时间的推移进行累积 对于您给出的示例,显式使用StringBuilder没有任何意义。(请查看第一个案例的

给定下面的2个
toString()
实现,首选哪一个:

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}

?


更重要的是,考虑到我们只有3个属性,这可能没有什么区别,但在什么情况下,您会从
+
concat切换到
StringBuilder

关键是您是在一个地方编写单个串联,还是随着时间的推移进行累积

对于您给出的示例,显式使用StringBuilder没有任何意义。(请查看第一个案例的编译代码。)

但如果您正在构建字符串(例如在循环中),请使用StringBuilder

为了澄清,假设HugarRay包含数千个字符串,代码如下:

...
String result = "";
for (String s : hugeArray) {
    result = result + s;
}
与以下各项相比,它非常浪费时间和内存:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();
我更喜欢:

String.format( "{a: %s, b: %s, c: %s}", a, b, c );
…因为它简短易读

我将为速度优化它,除非您在重复次数非常高的循环中使用它,并且已经测量了性能差异


我同意,如果您必须输出很多参数,这个表单可能会变得混乱(就像一条评论所说的)。在这种情况下,我会切换到一种更具可读性的形式(可能使用apache commons,取自matt b的答案),并再次忽略性能。

版本1更可取,因为它更短,并且没有任何性能差异

更重要的是,我们只有3个 属性,它可能不会产生错误 不同,但在哪一点上呢 从concat切换到builder


当您在循环中连接时-通常是编译器无法自行替换
StringBuilder

在大多数情况下,您不会看到这两种方法之间的实际差异,但很容易构建如下最坏情况:

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+="*";
    }
}
问题是to+=追加到字符串会重建一个新字符串,因此它的开销与字符串的长度(两者之和)成线性关系

因此,对于你的问题:

第二种方法速度更快,但可读性较差,更难维护。
正如我所说,在您的特定情况下,您可能看不到区别。

我可以指出,如果您要迭代一个集合并使用StringBuilder,您可能需要签出和(以不同的风格)

无论性能如何,它都将使您无需创建StringBuilder和for循环,这似乎是第一百万次了。

Apache Commons Lang有一个非常易于使用的类。它很好地处理了附加逻辑,并格式化了您希望toString的外观

public void toString() {
     ToStringBuilder tsb =  new ToStringBuilder(this);
     tsb.append("a", a);
     tsb.append("b", b)
     return tsb.toString();
}
将返回类似于
com.blah的输出。YourClass@abc1321f[a=whatever,b=foo]

或者使用链接以更精简的形式:

public void toString() {
     return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}
或者,如果要使用反射来包含类的每个字段:

public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

如果需要,还可以自定义ToString的样式。

对于我更喜欢使用的简单字符串

"string".concat("string").concat("string");

因此,我认为构造字符串的首选方法是使用StringBuilder,string#concat(),然后是重载的+运算符。当处理大字符串时,StringBuilder会显著提高性能,就像使用+运算符会大幅降低性能一样(随着字符串大小的增加,性能会呈指数级下降)。使用.concat()的一个问题是它可能引发NullPointerException。

尽可能使toString方法可读

在我的书中,唯一的例外是,如果你能向我证明它消耗了大量资源:)(是的,这意味着分析)


还请注意,Java5编译器生成的代码比Java早期版本中使用的手写“StringBuffer”方法更快。如果你使用“+”,这和未来的增强功能是免费的。

我还与我的老板在是否使用append或+”这一事实上发生了冲突。因为他们正在使用append(我仍然无法理解,就像他们每次创建新对象时所说的那样)。 所以我想做一些研发。虽然我喜欢迈克尔·博格沃德的解释,但我只是想解释一下,如果将来有人真的需要知道的话

/**
 *
 * @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());
    }
}
而上述类别的分解结果如下所示

 .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
; ---------------------------------------------------------------------------
.methodpublic()V//public Appc()方法
.限制堆栈2
.限制本地人2
met001_开始:;数据外部参照:met001\U slot000i
.第12行
aload_0;met001_slot000
调用特定的java/lang/Object。()V
.第13行
土发公司“没有名字”
astore_1;met001\U slot001
.第14行
met001_7:;数据外部参照:met001\U slot001i
新java/lang/StringBuilder//SB的第一个对象
重复
调用特殊的java/lang/StringBuilder。()V
aload_1;met001\U slot001
invokeVirtualJava/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/架线工;
ldc“我添加了一个名称我们可能需要更多的名称”
invokeVirtualJava/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/架线工;
aload_0;met001_slot000
invokeVirtualJava/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/架线工;
invokeVirtualJava/lang/StringBuilder.toString()Ljava/lang/String;
astore_1;met001\U slot001
.第15行
aload_1;met001\U slot001
aload_1;met001\U slot001
invokeVirtualJava/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
G
流行音乐
.第18行
return//不再创建SB
met001_结束:;数据外部参照:met001\U slot000i。。。
; ===========================================================================
;met001_slot000;数据外部参照:r。。。
.var 0是这个LAppc;从met001\u开始到met001\u结束
;met001_slot001;数据外部参照:+6w。。。
.var 1是x Ljava/lang/String;从met001\U 7到met001\U端
.结束方法
;44-1=44
; ---------------------------------------------------------------------------
; 段
/**
 *
 * @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());
    }
}
 .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
; ---------------------------------------------------------------------------
public String myCatPlus(String[] vals) {
    String result = "";
    for (String val : vals) {
        result = result + val;
    }
    return result;
}
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
public String myCatSb(String[] vals) {
    StringBuilder sb = new StringBuilder();
    for(String val : vals) {
        sb.append(val);
    }
    return sb.toString();
}
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
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);
}
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
static final int MAX_ITERATIONS = 50000;
static final int CALC_AVG_EVERY = 10000;

public static void main(String[] args) {
    printBytecodeVersion();
    printJavaVersion();
    case1();//str.concat
    case2();//+=
    case3();//StringBuilder
}

static void case1() {
    System.out.println("[str1.concat(str2)]");
    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() {
    System.out.println("[str1+=str2]");
    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() {
    System.out.println("[str1.append(str2)]");
    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();
    }
}
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";
}
usingStringConcatenation 29321 ms
usingStringBuilder 2 ms