StringBuilder vs Конкатенация строк в toString() в Java

Учитывая 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?

Ответ 1

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

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

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

Ответ 2

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

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

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

Чтобы уточнить, предполагая, что arrayArray содержит тысячи строк, код выглядит следующим образом:

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

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

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

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

Ответ 5

Я также столкнулся с моим боссом о том, следует ли использовать 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.

Ответ 6

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

Итак, для удобства чтения кода используйте "+".

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

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

Ответ 7

Используя последнюю версию 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.

Ответ 8

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

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

Ответ 9

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

public void toString() {
     ToStringBuilder tsb =  new ToStringBuilder(this);
     tsb.append("a", a);
     tsb.append("b", b)
     return tsb.toString();
}

Вернет вывод, который выглядит как [email protected][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, если хотите.

Ответ 10

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

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

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

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

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

Ответ 11

Сделайте метод toString доступным для чтения, как вы можете!

Единственное исключение для этого в моей книге - если вы можете доказать мне, что он потребляет значительные ресурсы:) (Да, это означает профилирование)

Также обратите внимание, что компилятор Java 5 генерирует более быстрый код, чем рукописный подход "StringBuffer", используемый в более ранних версиях Java. Если вы используете "+", это и будущие улучшения предоставляются бесплатно.

Ответ 12

Кажется, есть некоторые дебаты о том, что использование StringBuilder по-прежнему необходимо для текущих компиляторов. Поэтому я подумал, что дам 2 цента опыта.

У меня есть набор результатов JDBC из 10 тыс. записей (да, мне все они нужны в одной партии). Использование оператора + занимает около 5 минут на моей машине с помощью Java 1.8. Использование stringBuilder.append("") занимает менее секунды для одного и того же запроса.

Таким образом, разница огромна. Внутри цикла StringBuilder выполняется намного быстрее.

Ответ 13

См. Пример ниже:

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

static void case1() {
    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() {
    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() {
    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();
        }
}

Вывод:

среднее время для 10000 конкатенаций: 0,096 мс
среднее время для 10000 конкатенаций: 0,185 мс
среднее время для 10000 конкатенаций: 0,327 мс
среднее время для 10000 конкатенаций: 0,501 мс
среднее время для 10000 конкатенаций: 0,666 мс
Созданная строка длины: 1950000 в 17745 мс
среднее время для 10000 конкатенаций: 0,21 мс
среднее время для 10000 конкатенаций: 0,652 мс
среднее время для 10000 конкатенаций: 1.129 мс
среднее время для 10000 конкатенаций: 1,727 мс
среднее время для 10000 конкатенаций: 2.302 мс
Созданная строка длины: 1950000 в 60279 мс
среднее время для 10000 конкатенаций: 0,002 мс
среднее время для 10000 конкатенаций: 0,002 мс
среднее время для 10000 конкатенаций: 0,002 мс
среднее время для 10000 конкатенаций: 0,002 мс
среднее время для 10000 конкатенаций: 0,002 мс
Созданная строка длины: 1950000 в 100 мс

По мере увеличения длины строки также увеличивается время конкатенации.
Именно здесь определенная необходимость в StringBuilder.
Как вы видите, конкатенация: UUID.randomUUID()+"---", на самом деле не влияет на время.

PS: Я не думаю, что когда использовать StringBuilder в Java, это действительно дубликат.
Этот вопрос говорит о toString() который в большинстве случаев не выполняет конкатенации огромных строк.

Ответ 14

Консолидация строк по умолчанию с использованием "+" является более дорогостоящей, поскольку она должна сделать совершенно новую копию String, поскольку строки не изменяются в java. Это играет особую роль, если конкатенация очень частая, например: внутри цикла. Вот что предлагает моя ИДЕЯ, когда я пытаюсь сделать такое:

enter image description here

Основные правила:

  • В пределах одного присваивания строк использование конкатенации String прекрасное.
  • Если вы зацикливаете, чтобы создать большой блок символьных данных, перейдите к StringBuffer.
  • Использование + = на String всегда будет менее эффективным, чем использование StringBuffer, поэтому оно должно вызывать предупреждающие сигналы, но в некоторых случаях оптимизация будет незначительной по сравнению с проблемами читаемости, поэтому используйте здравый смысл.

Вот хороший блог Джона Скита по этой теме.

Ответ 15

Могу ли я указать, что если вы собираетесь перебирать коллекцию и использовать StringBuilder, вы можете проверить Apache Commons Lang и StringUtils.join() (в разных вариантах)?

Независимо от производительности, это избавит вас от необходимости создавать StringBuilders и для циклов для того, что кажется миллионным.

Ответ 16

Я сравнил четыре разных подхода, чтобы сравнить производительность. Я точно не знаю, что происходит с gc, но для меня важно время. Компилятор является важным фактором здесь. Я использовал jdk1.8.0_45 под платформой window8.1.

concatWithPlusOperator = 8
concatWithBuilder = 130
concatWithConcat = 127
concatStringFormat = 3737
concatWithBuilder2 = 46


public class StringConcatenationBenchmark {

private static final int MAX_LOOP_COUNT = 1000000;

public static void main(String[] args) {

    int loopCount = 0;
    long t1 = System.currentTimeMillis();
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithPlusOperator();
        loopCount++;
    }
    long t2 = System.currentTimeMillis();
    System.out.println("concatWithPlusOperator = " + (t2 - t1));

    long t3 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithBuilder();
        loopCount++;
    }
    long t4 = System.currentTimeMillis();
    System.out.println("concatWithBuilder = " + (t4 - t3));

    long t5 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithConcat();
        loopCount++;
    }
    long t6 = System.currentTimeMillis();
    System.out.println("concatWithConcat = " + (t6 - t5));

    long t7 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatStringFormat();
        loopCount++;
    }
    long t8 = System.currentTimeMillis();
    System.out.println("concatStringFormat = " + (t8 - t7));

    long t9 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithBuilder2();
        loopCount++;
    }
    long t10 = System.currentTimeMillis();
    System.out.println("concatWithBuilder2 = " + (t10 - t9));
}

private static void concatStringFormat() {
    String s = String.format("%s %s %s %s ", "String", "String", "String", "String");
}

private static void concatWithConcat() {
    String s = "String".concat("String").concat("String").concat("String");
}

private static void concatWithBuilder() {
    StringBuilder builder=new StringBuilder("String");
    builder.append("String").append("String").append("String");
    String s = builder.toString();
}

private static void concatWithBuilder2() {
    String s = new StringBuilder("String").append("String").append("String").append("String").toString();
}

private static void concatWithPlusOperator() {
    String s = "String" + "String" + "String" + "String";
}
}

Ответ 17

Вот что я проверял в Java8

  • Использование конкатенации строк
  • Использование StringBuilder

    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

Ответ 18

Думаю, нам стоит пойти с подходом StringBuilder append. Причина

  • Конкатенация String будет каждый раз создавать новый строковый объект (As String - неизменяемый объект), поэтому он создаст 3 объекта.

  • С построителем String будет создан только один объект [StringBuilder is muttable], и ​​к нему добавляется дополнительная строка.

Ответ 19

Для простых строк, которые я предпочитаю использовать

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

В порядке, я бы сказал, предпочтительным методом построения строки является использование StringBuilder, String # concat(), а затем перегруженный + оператор. StringBuilder - значительное увеличение производительности при работе с большими строками, так же как и с использованием оператора +, - это значительное снижение производительности (экспоненциально большое уменьшение по мере увеличения размера строки). Одна проблема с использованием .concat() заключается в том, что он может вызывать NullPointerExceptions.