StringBuilder vs об'єднання рядків у toString () на Java


925

Враховуючи дві 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 властивості, це може не змінити значення, але в який момент ви переходите з +конкоматів на StringBuilder?


40
У який момент ви переходите на StringBuilder? Коли це впливає на пам'ять або продуктивність. Або коли це може. Якщо ви справді робите це лише для декількох струн один раз, не хвилюйтесь. Але якщо ви будете робити це знову і знову, вам слід побачити помірну різницю при використанні StringBuilder.
ewall

що означає середнє значення параметра 100?
Асиф Муштак

2
@UnK вядомы 100 - початковий розмір StringBuilder
не послідовник

@nonsequitor Отже, максимум символів буде 100?
Асиф Муштак

10
@ Невідомий не лише початковий розмір, якщо ви знаєте приблизний розмір рядка, з яким ви маєте справу, то ви можете сказати, StringBuilderякий розмір потрібно виділити наперед, інакше, якщо у нього не вистачить місця, доведеться подвоїти розмір, створивши Новий char[]масив потім скопіювати дані - що коштує дорого. Ви можете обдурити, задавши розмір, і тоді немає необхідності в створенні цього масиву - тому, якщо ви думаєте, що ваша строка буде довжиною ~ 100 символів, то ви можете встановити StringBuilder до цього розміру, і він ніколи не повинен буде розширюватися внутрішньо.
не послідовник

Відповіді:


964

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

Що ще важливіше, враховуючи, що у нас є лише 3 властивості, це може не змінити значення, але в який момент ви переходите від concat до builder?

У тому місці, коли ви об'єднуєтесь у циклі - зазвичай це коли компілятор не може замінити StringBuilderсебе.


19
Це правда, але посилання на мову також говорить, що це необов’язково. Насправді, я просто зробив простий тест з JRE 1.6.0_15, і я не бачив оптимізації компілятора в класі, декомпільованому.
bruno conde

37
Я просто спробував код з питання (складений на JDK 1.6.0_16) і знайшов оптимізацію, як очікувалося. Я впевнений, що всі сучасні компілятори це зроблять.
Майкл Боргвардт

22
Ви праві. Дивлячись на байт-код, я чітко бачу оптимізацію StringBuilder. Я використовував декомпілятор, і, як це було, він перетворюється назад у concat. +1
bruno conde

80
Щоб не побити мертвого коня, але формулювання в специфікації таке: To increase the performance of repeated string concatenation, a Java compiler _may_ use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.Ключове слово, що існує, може бути . З огляду на те, що це офіційно необов’язково (хоча це, швидше за все, здійснено), чи не слід захищати себе?
Лукас

93
@Lucas: Ні, ми не повинні. Якщо компілятор вирішить не виконувати цю оптимізацію, це буде тому, що цього не варто. У 99% випадків компілятор краще знає, яка оптимізація того варта, тому, як правило, розробник не повинен заважати. Звичайно, ваша ситуація може потрапити до інших 1%, але це можна перевірити лише шляхом (ретельного) тестування.
sleske

255

Ключовим є те, ви пишете одну конкатенацію в одному місці або накопичуєте її з часом.

У прикладі, який ви навели, немає сенсу в явному використанні StringBuilder. (Подивіться на складений код для вашого першого випадку.)

Але якщо ви будуєте рядок, наприклад, всередині циклу, використовуйте StringBuilder.

Для уточнення, припускаючи, що величезнийArray містить тисячі рядків, код таким чином:

...
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
Так, StringBuilder не потрібно заново створювати об'єкт String.
Ольга

124
Чорт, я використав ці 2 функції для тестування великої струни, над якою працюю. 6.51min vs 11secs
користувач1722791

1
До речі, ви також можете скористатися result += s;(у першому прикладі)
RAnders00,

1
скільки об’єкта буде створено цим твердженням? "{a:"+ a + ", b:" + b + ", c: " + c +"}";
Асиф Муштак

1
А як щодо чогось типу: String str = (a == null)? null: a '+ (b == null)? null: b '+ (c == null)? c: c '+ ...; ? Чи буде це заважати оптимізації відбуватися?
amitfr

75

Я віддаю перевагу:

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

... тому що він короткий і читабельний.

Я б не оптимізував це для швидкості, якщо ви не використовуєте його всередині циклу з дуже високою кількістю повторень і не виміряли різницю в продуктивності.

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


64
Це насправді довше, містить більше символів і містить змінні текст поза послідовністю.
Том Хоутін - таклін

4
Так ви б сказали, що це менш читабельно, ніж один з інших підходів?
тангенс

3
Я вважаю за краще писати це, тому що простіше додати більше змінних, але я не впевнений, що це читабельніше - тим більше, що кількість аргументів збільшується. Він також не працює, коли вам потрібно додати біти в різний час.
Алекс Фейнман

78
Здається, важче читати (для мене). Тепер мені потрібно сканувати туди і назад між {...} та параметрами.
Стів Куо

10
Я віддаю перевагу цій формі, тому що це безпечно, якщо одним із параметрів єnull
rds

74

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

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

Проблема полягає в тому, що додавання + = до рядка реконструює новий рядок, тому це коштує чогось лінійного до довжини ваших рядків (сума обох).

Отже - до вашого питання:

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


Не забувайте про .concat (). Я б припустив, що минулий час буде десь від 10 до 18 мс, що робить його мізерним при використанні коротких рядків, як оригінальний приклад публікації.
Droo

9
Поки ви маєте рацію +=, оригінальним прикладом була послідовність того +, що компілятор перетворюється на один string.concatвиклик. Результати не застосовуються.
Сліпий

1
@Blindy & Droo: - Ви обидва маєте рацію. Використання .concate в такому сценарії найкраще вирішити, оскільки + = створює новий об'єкт щоразу, коли виконується рутинний цикл.
perilbrain

3
чи знаєте ви, що його toString () не викликається в циклі?
Омрі Ядан

Я спробував цей приклад, щоб перевірити швидкість. Отже, мої результати такі: повільно пройшло 29672 мс; швидко минуло 15 мс. Тож відповідь очевидна. Але якщо це було б 100 ітерацій - час такий же - 0 мс. Якщо 500 ітерацій - 16 мс і 0 мс. І так далі.
Ернестас Груодіс

28

Я також зіткнувся зі своїм начальником щодо того, чи слід використовувати додавання або +. Як вони використовують додавання (я все ще не можу зрозуміти, як кажуть, щоразу, коли створюється новий об’єкт). Тому я подумав зробити деякі дослідження та розробки. Хоча я люблю пояснення Майкла Боргвардта, але просто хотів показати пояснення, якщо комусь справді потрібно буде знати в майбутньому.

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


27

Оскільки Java 1.5, просте з'єднання одного рядка з "+" та StringBuilder.append () генерує абсолютно однаковий байт-код.

Тож заради читабельності коду використовуйте "+".

2 винятки:

  • багатопотокове середовище: StringBuffer
  • конкатенація в циклі: StringBuilder / StringBuffer

22

Використовуючи останню версію 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циклі.


1
Це Oracle JDK або OpenJDK?
Крістоф Руссі

12

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

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


9

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

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


9

Дивіться приклад нижче:

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();
    }
}

Вихід:

версія байт-коду Java: 8
java.version: 1.8.0_144
[str1.concat (str2)]
середній час для 10000 конкатенацій: 0,06 мс
середній час середнього часу для 10000 конкатенацій: 0,185 мс
середній час для 10000 конкатенацій: 0,327 мс середній
середній час для 10000 конкатенацій: 0.501 мс
середній час середнього за 10000 конкатенацій: 0.656 мс сер.
Створений рядок довжиною: 1950000 в 17745 мс
[str1 + = str2]
середній час для 10000 конкатенацій:
середній час 0,21 мс середній час для 10000 конкатенацій: 0,652 мс середній
середній час для 10000 конкатенацій: 1.129 мс
середній час для 10000 конкатенацій: середньо 1.727 мс
середній час для 10000 конкатенацій: 2.302 мс сер.
Створений рядок довжиною: 1950000 за 60279 мс
[str1.append (str2)]
середній час для 10000 конкатенацій: 0,002 мс
середній час середнього часу для 10000 конкатенацій: 0,002 мс середній
середній час для 10000 конкатенацій:
Середній час 0,002 мс середній час для 10000 конкатенацій: 0,002 мс
середній час середнього часу для 10000 конкатенацій: 0,002 мс середній
Створений рядок довжиною: 1950000 в 100 мс

Зі збільшенням довжини рядка збільшується і час конкатенації.
Саме тут StringBuilder, безумовно, потрібно.
Як бачите, конкатенація:, UUID.randomUUID()+"---"насправді не впливає на час.

PS: Я не думаю, що коли використовувати StringBuilder на Java , насправді це дублікат.
Це питання говорить про те, toString()яке більшість разів не виконує конкатекації величезних струн.


Оновлення 2019 року

З java8часів справи дещо змінилися. Здається, що зараз (java13) час конкатенації +=практично такий же, як і str.concat(). Однак StringBuilderчас конкатенації все ще постійний . (Оригінальна публікація вище була трохи відредагована, щоб додати більше детального виводу)

версія байт-коду java: 13
java.version: 13.0.1
[str1.concat (str2)]
середній час для 10000 конкатенацій: 0,047 ms
середній час для 10000 конкатенацій: 0,1 мс
середній час для 10000 конкатенацій: 0,17 мс середній
середній час для 10000 конкатенацій:
середній час 0,255 мс середній час для 10000 конкатенацій: 0,336 мс сер.
Створений рядок довжиною: 1950000 в 9147 мс
[str1 + = str2]
середній час для 10000 конкатенацій: 0,037 мс
середній час для 10000 конкатенацій: 0,097 мс середній
середній час за 10000 конкатенацій:
середній час 0,249 мс середній час для 10000 конкатенацій: серп. 0,298 мс
середній час для 10000 конкатенацій: 0,326 мс сер.
Створений рядок довжиною: 1950000 за 10191 мс
[str1.append (str2)]
середній час для 10000 конкатенацій: 0,001 мс
середній час середнього часу для 10000 конкатенацій: 0,001 мс середній
середній час для 10000 конкатенацій:
Середній час 0,001 мс середнього часу для 10000 конкатенацій: 0,001 мс
середній час середнього часу для 10000 конкатенацій: 0,001 мс середній
Створений рядок довжиною: 1950000 за 43 мс

Варто зауважити, що bytecode:8/java.version:13комбінація має хороші переваги щодо продуктивностіbytecode:8/java.version:8


Це повинна бути прийнята відповідь. Це залежить від розміру String Stream, який визначає вибір concat або StringBuilder
user1428716

@ user1428716 FYI: відповідь оновлено результатами, від java13яких тепер відрізняються. Але я думаю, що головний висновок залишається тим самим.
Марінос

7

Apache Commons-Lang має клас ToStringBuilder, який дуже простий у використанні. Це гарна робота як з обробкою логіки додавання, так і форматуванням того, як ви хочете виглядати ваш 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, якщо хочете.


4

Зробіть метод toString максимально зрозумілим!

Єдиним винятком цього в моїй книзі є те, якщо ви можете довести мені, що вона споживає значні ресурси :) (Так, це означає профілювання)

Також зауважте, що компілятор Java 5 генерує швидший код, ніж рукописний підхід "StringBuffer", який використовується в попередніх версіях Java. Якщо ви використовуєте "+", це та майбутні удосконалення виходить безкоштовно.


3

Здається, є певні дискусії щодо того, чи потрібно використовувати StringBuilder з поточними компіляторами. Тому я подумав, що дам свої 2 копійки досвіду.

У мене є JDBCнабір результатів 10k записів (так, мені потрібні всі в одній партії.) Використання оператора + займає на моїй машині близько 5 хвилин Java 1.8. Для використання stringBuilder.append("")цього ж запиту потрібно менше секунди.

Тож різниця величезна. Всередині петля StringBuilderвідбувається набагато швидше.


2
Я думаю, що дискусія стосується використання його поза петлями. Я думаю, що існує консенсус, що потрібно використовувати його в циклі.
Йорданія

2

Об’єднання рядків із застосуванням "+" - це дорожче, оскільки він повинен зробити цілком нову копію String, оскільки струни незмінні в Java. Це відіграє особливу роль, якщо конкатенація дуже часта, наприклад: всередині циклу. Далі, що пропонує мій IDEA, коли я намагаюся зробити таке:

введіть тут опис зображення

Загальні правила:

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

Ось приємний блог Джона Скіта навколо цієї теми.


2
Ніколи не слід використовувати StringBuffer, якщо ви абсолютно не потребуєте синхронізованого доступу з декількох потоків. В іншому випадку віддайте перевагу StringBuilder, який не синхронізований і, таким чином, має менші витрати.
Еркі дер Луні

1

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

Незалежно від продуктивності, це позбавить вас від необхідності створити StringBuilders і циклів для того, що здається мільйонним разом.


1

Ось що я перевірив у 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

-1

Думаю, нам слід рухатись із підходом StringBuilder. Причина:

  1. Конкатенат String створюватиме новий об'єкт рядка кожного разу (як String є незмінним об'єктом), тому він створить 3 об'єкти.

  2. З String builder буде створений лише один об'єкт [StringBuilder є змінним], і подальший рядок додається до нього.


Чому ця відповідь спростована? docs.oracle.com/javase/8/docs/api/java/util/stream/… - зменшення
змінних

-4

Для таких простих струн я вважаю за краще використовувати

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

Для того, щоб сказати, що кращим методом побудови рядка є використання StringBuilder, String # concat (), потім оператора перевантаженого +. StringBuilder - це значне підвищення продуктивності при роботі великих рядків так само, як використання оператора + - це велике зниження продуктивності (експоненціально велике зменшення у міру збільшення розміру String). Одна з проблем використання .concat () полягає в тому, що він може кидати NullPointerExceptions.


9
Використання concat (), швидше за все, буде гірше, ніж "+", оскільки JLS дозволяє "+" перетворити на StringBuilder, і, швидше за все, всі JVM роблять це або використовують більш ефективну альтернативу - те ж саме, ймовірно, не буде concat, який повинен створити і відкинути принаймні одну повну проміжну рядок у вашому прикладі.
Лоуренс Дол
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.