StringBuilder против конкатенации строк в toString() в Java
У меня возник вопрос о том, какой из двух вариантов реализации метода 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 ответ(ов)
Ключевой момент заключается в том, пишете ли вы одно единственное конкатенированное выражение в одном месте или накапливаете строку со временем.
В приведенном вами примере нет смысла использовать 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 для повышения производительности.
В большинстве случаев вы не заметите значительной разницы между двумя подходами, но легко можно представить крайний случай, подобный этому:
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
Проблема заключается в том, что при использовании оператора +=
для конкатенации строк происходит реконструкция новой строки, что приводит к затратам, пропорциональным длине ваших строк (сумме обеих строк).
Так что, отвечая на ваш вопрос:
Второй подход будет быстрее, но он менее читаем и сложнее в поддержке. Как я уже упоминал, в вашем конкретном случае вы, вероятно, не увидите разницы.
С момента появления Java 1.5 однострочная конкатенация с помощью оператора "+" и метод StringBuilder.append()
генерируют точно такой же байткод.
Таким образом, для повышения читаемости кода предпочтительнее использовать оператор "+".
Однако есть два исключения:
- Многопоточная среда: используйте
StringBuffer
, который является потокобезопасным. - Конкатенация в циклах: в этом случае лучше использовать
StringBuilder
илиStringBuffer
, чтобы избежать создания множества промежуточных объектов строк, что может привести к снижению производительности.
Вам действительно удалось поднять интересный вопрос о выборе между использованием 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
по сравнению с оператором +
.
Когда вы используете последнюю версию 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
, вы избегаете лишнего создания объектов и увеличения нагрузки на сборщик мусора, что делает ваш код более эффективным.
Как преобразовать строку в int в Java?
Как разделить строку в Java?
Преобразование 'ArrayList<String>' в 'String[]' в Java
Как прочитать большой текстовый файл построчно с помощью Java?
Как сгенерировать случайную алфавитно-цифровую строку