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?

18 ответов


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

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

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


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

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

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

чтобы уточнить, предполагая, что hugeArray содержит тысячи строк, код выглядит так:

...
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 );

...потому что она короткая и читается.

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

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


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

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

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

Так на ваш вопрос:

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


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

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


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

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

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

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

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

байт-код: (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.добавить

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 создается для каждой итерации цикла for и генерируемый результат сохраняется, выполняя toString() вызов(с 29 по 41). Таким образом, вы генерируете промежуточные строки, которые вам действительно не нужны при использовании + оператор for петля.


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


по соображениям производительности, использование += (String конкатенация) не рекомендуется. Причина почему: 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 конкатенаций штраф за производительность актуален; для тысяч конкатенаций разница огромна.

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


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

идея состоит в том, чтобы заменить весь StringBuilder append dance простым вызовом invokedynamic на java.ленг.взывать.StringConcatFactory, который будет принимать значения, нуждающиеся в конкатенации.


сделайте метод toString как можно более читаемым!

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

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


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

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

таким образом, разница огромна. Внутри петли StringBuilder - это гораздо быстрее.


см. пример ниже:

//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 ms avg
среднее время для 10000 конкатенаций: 0,185 МС avg
среднее время для 10000 конкатенаций: 0,327 МС avg
среднее время для 10000 конкатенаций: 0.501 МС avg
среднее время для 10000 конкатенаций: 0,656 МС avg
Создана строка длины: 1950000 в 17745 МС
среднее время для 10000 конкатенаций: 0.21 ms avg
среднее время для 10000 конкатенаций: 0,652 МС avg
среднее время для 10000 конкатенаций: 1,129 МС avg
среднее время для 10000 конкатенаций: 1.727 МС avg
среднее время для 10000 конкатенаций: 2.302 МС avg
Создана строка длины: 1950000 в 60279 ms
среднее время для 10000 конкатенаций: 0.002 ms avg
среднее время 10000 связи: 0.002 МС avg
среднее время для 10000 конкатенаций: 0.002 ms avg
среднее время для 10000 конкатенаций: 0.002 ms avg
среднее время для 10000 конкатенаций: 0.002 ms avg
Создана строка длины: 1950000 в 100 мс

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

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


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

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


Я сравнила четыре разных подхода для сравнения производительности. Я точно не знаю, что происходит с 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";
}
}

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

enter image description here

Общие Правила:

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

здесь хороший блог Джона Скита вокруг этой темы.


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

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

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


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

  1. строка concatenate будет создавать новый объект string каждый раз (поскольку строка является неизменяемым объектом) , поэтому он создаст 3 объекта.

  2. с строку застройщика только один объект будет создан[класса StringBuilder является muttable] и далее строки, которые добавляются к нему.