StringBuilder против конкатенации строк в toString () в Java - PullRequest
851 голосов
/ 07 октября 2009

Учитывая 2 toString() реализации ниже, какая из них предпочтительна:

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

или

public String toString(){
    StringBuilder sb = new StringBuilder(100);
    return sb.append("{a:").append(a)
          .append(", b:").append(b)
          .append(", c:").append(c)
          .append("}")
          .toString();
}

Что еще более важно, учитывая, что у нас есть только 3 свойства, это может не иметь значения, но в какой момент вы бы переключились с + concat на StringBuilder?

Ответы [ 18 ]

886 голосов
/ 07 октября 2009

Версия 1 предпочтительнее, потому что она короче, а компилятор фактически превратит ее в версию 2 - без разницы в производительности.

Что более важно, учитывая, что у нас есть только 3 свойства это не может сделать разница, но в какой момент ты переключиться с concat на builder?

В тот момент, когда вы объединяете в цикле - обычно, когда компилятор не может заменить StringBuilder сам по себе.

229 голосов
/ 07 октября 2009

Ключ в том, пишете ли вы одну конкатенацию в одном месте или накапливаете ее с течением времени.

В приведенном вами примере нет смысла явно использовать StringBuilder. (Посмотрите скомпилированный код для вашего первого случая.)

Но если вы строите строку, например внутри цикла используйте StringBuilder.

Для пояснения, если предположить, что огромный массив содержит тысячи строк, код наподобие этого:

...
String result = "";
for (String s : hugeArray) {
    result = result + s;
}

очень трата времени и памяти по сравнению с:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();
71 голосов
/ 07 октября 2009

Я предпочитаю:

String.format( "{a: %s, b: %s, c: %s}", a, b, c );

... потому что он короткий и читаемый.

Я бы не оптимизировал бы это для скорости, если только вы не используете его внутри цикла с очень высоким числом повторений и не измерили разницу в производительности.

Я согласен, что если вам нужно вывести много параметров, эта форма может запутаться (как говорится в одном из комментариев). В этом случае я бы переключился на более читаемую форму (возможно, используя ToStringBuilder apache-commons - взятый из ответа Мэтта b) и снова проигнорировал бы производительность.

67 голосов
/ 07 октября 2009

В большинстве случаев вы не увидите реальной разницы между двумя подходами, но наихудший сценарий легко построить, как этот:

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+="*";
    }
}

Вывод:

slow elapsed 11741 ms
fast elapsed 7 ms

Проблема в том, что добавление + = к строке восстанавливает новую строку, поэтому она стоит что-то линейное по отношению к длине ваших строк (сумма обоих).

Итак - на ваш вопрос:

Второй подход был бы быстрее, но он менее читабелен и сложнее в обслуживании. Как я уже сказал, в вашем конкретном случае вы, вероятно, не увидите разницу.

27 голосов
/ 22 декабря 2011

У меня также был конфликт с моим боссом по поводу того, использовать ли append или +. Поскольку они используют Append (я все еще не могу понять, как они говорят каждый раз, когда создается новый объект). Поэтому я подумал сделать несколько R & D. Хотя мне нравится объяснение Майкла Боргвардта, но я просто хотел показать объяснение, если кому-то действительно понадобится знать в будущем.

/**
 *
 * @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
; ---------------------------------------------------------------------------

Из двух приведенных выше кодов видно, что Майкл прав. В каждом случае создается только один объект SB.

24 голосов
/ 16 апреля 2012

Начиная с Java 1.5, простая конкатенация в одну строку с "+" и StringBuilder.append () генерируют точно такой же байт-код.

Так что для удобства чтения кода используйте «+».

2 исключения:

  • многопоточная среда: StringBuffer
  • конкатенация в циклах: StringBuilder / StringBuffer
22 голосов
/ 26 мая 2015

При использовании последней версии Java (1.8) разборка (javap -c) показывает оптимизацию, представленную компилятором. + также sb.append() будет генерировать очень похожий код. Тем не менее, стоит проверить поведение, если мы используем + в цикле for.

Добавление строк с использованием + в цикле for

Java:

public String myCatPlus(String[] vals) {
    String result = "";
    for (String val : vals) {
        result = result + val;
    }
    return result;
}

ByteCode: (for отрывок цикла)

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

Добавление строк с использованием stringbuilder.append

Java:

public String myCatSb(String[] vals) {
    StringBuilder sb = new StringBuilder();
    for(String val : vals) {
        sb.append(val);
    }
    return sb.toString();
}

ByteCdoe: (for отрывок цикла)

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

Есть небольшая явная разница . В первом случае, когда использовался +, для каждой итерации цикла создается новый StringBuilder, а сгенерированный результат сохраняется с помощью вызова toString() (с 29 по 41). Таким образом, вы генерируете промежуточные строки, которые вам действительно не нужны при использовании оператора + в цикле for.

9 голосов
/ 03 апреля 2017

В Java 9 версия 1 должна быть быстрее, потому что она преобразуется в вызов invokedynamic. Более подробную информацию можно найти в JEP-280 :

Идея состоит в том, чтобы заменить весь танец добавления StringBuilder простым вызовом динамического вызова java.lang.invoke.StringConcatFactory, который будет принимать значения, требующие объединения.

7 голосов
/ 21 апреля 2016

Из соображений производительности не рекомендуется использовать += (String сцепление). Причина в том, что Java String является неизменной, каждый раз, когда выполняется новая конкатенация, создается новая String (новая имеет отпечаток, отличный от старого, уже в пуле строк ). Создание новых строк оказывает давление на ГХ и замедляет программу: создание объектов стоит дорого.

Код ниже должен сделать его более практичным и понятным одновременно.

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

Не учитывая результаты для одной конкатенации (JIT еще не выполнил свою работу), даже для 10 конкатенаций снижение производительности является значимым; для тысяч конкатенаций разница огромна.

Уроки, извлеченные из этого очень быстрого эксперимента (легко воспроизводимого с помощью приведенного выше кода): никогда не используйте += для объединения строк вместе, даже в самых простых случаях, когда требуется несколько объединений (как сказано, создание новых строк стоит дорого во всяком случае и оказывает давление на ГК).

7 голосов
/ 07 октября 2009

Apache Commons-Lang имеет класс ToStringBuilder , который очень прост в использовании. Он отлично справляется как с логикой добавления, так и с форматированием того, как вы хотите, чтобы ваша строка была похожа.

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, если хотите.

...