11

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

16

У меня возник вопрос о том, какой из двух вариантов реализации метода 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? И существует ли предел, после которого использование StringBuilder становится более целесообразным с точки зрения производительности?

5 ответ(ов)

3

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

В приведенном вами примере нет смысла использовать 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();

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

0

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

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 < 100_000; i++)
            s.append("*");      
    }

    private static void slow()
    {
        String s = "";
        for (int i = 0; i < 100_000; i++)
            s += "*";
    }
}

Вывод будет примерно следующим:

slow elapsed 11741 ms
fast elapsed 7 ms

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


Так что, отвечая на ваш вопрос:

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

0

С момента появления Java 1.5 однострочная конкатенация с помощью оператора "+" и метод StringBuilder.append() генерируют точно такой же байткод.

Таким образом, для повышения читаемости кода предпочтительнее использовать оператор "+".

Однако есть два исключения:

  1. Многопоточная среда: используйте StringBuffer, который является потокобезопасным.
  2. Конкатенация в циклах: в этом случае лучше использовать StringBuilder или StringBuffer, чтобы избежать создания множества промежуточных объектов строк, что может привести к снижению производительности.
0

Вам действительно удалось поднять интересный вопрос о выборе между использованием append и оператором + в Java. Давайте разберемся, какой подход более эффективен и почему.

Как вы правильно заметили, ваш босс предпочитает использовать метод append из StringBuilder, и это действительно имеет свои обоснования. Когда мы используем оператор +, каждый раз, когда мы конкатенируем строки, создаётся новый объект String, что вызывает дополнительные накладные расходы на управление памятью.

В вашем коде с помощью метода Sb вы используете StringBuilder, который позволяет эффективно накапливать строки без создания нового объекта на каждом шаге, что видно в disassembly:

new java/lang/StringBuilder // Создание первого экземпляра StringBuilder
...
invokestatic java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;

С другой стороны, когда вы пользуетесь оператором +=:

String x = "no name";
x += "I have Added a name" + "We May need few more names" + Appc.this;

При этом в Java происходит создание промежуточных строк, и в основании видим код для создания новых объектов строк.

В заключение, как показано в вашем примере, использование StringBuilder предпочтительнее в ситуациях, где нужно многократно соединять строки, поскольку это снижает количество создаваемых объектов и тем самым повышает производительность. Правильное использование append особенно важно в циклах или при работе с большим объемом данных.

Спасибо за ваш вклад в обсуждение этой темы! Надеюсь, это поможет другим разработчикам разобраться в преимуществах использования StringBuilder по сравнению с оператором +.

0

Когда вы используете последнюю версию 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();
}

ByteCode (отрывок из цикла 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. Используя StringBuilder, вы избегаете лишнего создания объектов и увеличения нагрузки на сборщик мусора, что делает ваш код более эффективным.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь