Как Java делает использование конкатенации строк “+”?

Я читал о способе, с которым работает Java += оператор, с помощью StringBuilder.
Действительно ли это - то же с a ("a" + "b") операция?

10
задан George Kagan 6 November 2016 в 20:03
поделиться

6 ответов

Нет. Использование StringBuilder не то же самое, что выполнение «a» + «b» .

В Java экземпляры String неизменяемы.

Итак, если вы это сделаете:

String c = "a" + "b";

Вы создаете новые строки каждый раз при объединении.

С другой стороны, StringBuilder похож на буфер, который может увеличиваться по мере необходимости при добавлении новых строк.

StringBuilder c = new StringBuilder();
c.append("a");
c.append("b"); // c is only created once and appended "a" and "b".

Практическое правило (изменено благодаря комментариям, которые я получил):

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

Оптимизация компилятора также играет огромную роль при компиляции такого кода.

Вот дальнейшее объяснение по теме.

И другие вопросы StackOVerflow по этой проблеме:

Лучше ли повторно использовать StringBuilder в цикле?

Как лучше всего построить строку элементов с разделителями в Java?

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

14
ответ дан 3 December 2019 в 13:18
поделиться

Если вы объедините буквальные строки (буквально «foo» + «bar» ), компилятор сделает это во время компиляции, а не во время выполнения.

Если у вас есть две нелитеральные строки и соедините их с помощью + , компилятор (во всяком случае, Sun) будет использовать StringBuilder под оболочкой, но не обязательно в большинстве эффективный способ. Так, например, если у вас есть это:

String repeat(String a, int count) {
    String rv;

    if (count <= 0) {
        return "";
    }

    rv = a;
    while (--count > 0) {
        rv += a;
    }
    return rv;
}

... то, что компилятор Sun на самом деле создаст в виде байт-кода, выглядит примерно так :

String repeat(String a, int count) {
    String rv;

    if (count <= 0) {
        return "";
    }

    rv = a;
    while (--count > 0) {
        rv = new StringBuilder().append(rv).append(a).toString();
    }
    return rv;
}

(Да, действительно - см. Разборку в конце этот ответ.) Обратите внимание, что он создавал новый StringBuilder на каждой итерации, а затем преобразовывал результат в String . Это неэффективно (но это не имеет значения, если вы не делаете это лот ) из-за всех выделений временной памяти: он выделяет StringBuilder и его буфер, вполне возможно перераспределяет буфер в первом добавлении [если rv имеет длину более 16 символов, что является размером буфера по умолчанию] и если не в первом, то почти наверняка во втором ] append , затем выделяет String в конце - а затем делает это снова на следующей итерации.

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

String repeat(String a, int count) {
    StringBuilder rv;

    if (count <= 0) {
        return "";
    }

    rv = new StringBuilder(a.length() * count);
    while (count-- > 0) {
        rv.append(a);
    }
    return rv.toString();
}

Здесь мы использовали явный StringBuilder , а также установили его начальную емкость буфера. быть достаточно большим, чтобы удерживать результат. Это более эффективно с точки зрения памяти, но, конечно, немного менее понятно для неопытных разработчиков кода и немного сложнее для написания. Так что , если вы обнаружите проблему с производительностью из-за жесткого цикла конкатенации строк, это может быть одним из способов ее решения.

Вы можете увидеть этот скрытый StringBuilder в действии с помощью следующего тестового класса:

public class SBTest
{
    public static final void main(String[] params)
    {
        System.out.println(new SBTest().repeat("testing ", 4));
        System.exit(0);
    }

    String repeat(String a, int count) {
        String rv;

        if (count <= 0) {
            return "";
        }

        rv = a;
        while (--count > 0) {
            rv += a;
        }
        return rv;
    }
}

... который дизассемблирует (с помощью javap -c SBTest ) следующим образом :

Compiled from "SBTest.java"
public class SBTest extends java.lang.Object{
public SBTest();
Code:
   0: aload_0
   1: invokespecial  #1; //Method java/lang/Object."<init>":()V
   4: return

public static final void main(java.lang.String[]);
Code:
   0: getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3: new   #3; //class SBTest
   6: dup
   7: invokespecial  #4; //Method "<init>":()V
   10: ldc   #5; //String testing
   12: iconst_4
   13: invokevirtual  #6; //Method repeat:(Ljava/lang/String;I)Ljava/lang/String;
   16: invokevirtual  #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   19: iconst_0
   20: invokestatic   #8; //Method java/lang/System.exit:(I)V
   23: return

java.lang.String repeat(java.lang.String, int);
Code:
   0: iload_2
   1: ifgt  7
   4: ldc   #9; //String
   6: areturn
   7: aload_1
   8: astore_3
   9: iinc  2, -1
   12: iload_2
   13: ifle  38
   16: new   #10; //class java/lang/StringBuilder
   19: dup
   20: invokespecial  #11; //Method java/lang/StringBuilder."<init>":()V
   23: aload_3
   24: invokevirtual  #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   27: aload_1
   28: invokevirtual  #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   31: invokevirtual  #13; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   34: astore_3
   35: goto  9
   38: aload_3
   39: areturn

}

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

Все это временное выделение памяти звучит некрасиво, но опять же, только если вы имеете дело с существенными циклами и / или значительными строками. Кроме того, когда полученный байт-код запускается, JVM вполне может оптимизировать его дальше. Например, Sun HotSpot JVM - это очень зрелый оптимизирующий компилятор JIT. Как только он определил цикл как горячую точку, он вполне может найти способ его реорганизовать. Или нет, конечно. : -)

Мое практическое правило: я беспокоюсь об этом, когда вижу проблему с производительностью или знаю, что выполняю много конкатенации, и это очень вероятно будет проблемой с производительностью, и если я использую вместо этого StringBuilder , код не будет существенно затронут с точки зрения ремонтопригодности.Лига яростных противников преждевременной оптимизации, вероятно, не согласится со мной по второму из них. : -)

36
ответ дан 3 December 2019 в 13:18
поделиться

Строки чаще объединяются с оператором +, как в "Привет," + "мир" + "!"

Источник

-2
ответ дан 3 December 2019 в 13:18
поделиться

Да, это то же самое, но компилятор может дополнительно оптимизировать конкатенацию литералов перед выдачей кода, поэтому "a "+" b " может быть выдано напрямую как " ab ".

5
ответ дан 3 December 2019 в 13:18
поделиться

Для конкатенации фиксированного количества строк в одном выражении с + компилятор создаст код, используя одиночный StringBuilder .

Например. строка

String d = a + b + c;

дает тот же байт-код, что и строка

String d = new StringBuilder().append(a).append(b).append(c).toString();

при компиляции с использованием компилятора javac. (Компилятор Eclipse создает несколько более оптимизированный код, вызывая new StringBuilder (a) , тем самым сохраняя один вызов метода.)

Как упоминалось в других ответах, компилятор объединит строковые литералы, такие как " a "+" b " в одну строку, создавая вместо этого байт-код, содержащий " ab ".

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

4
ответ дан 3 December 2019 в 13:18
поделиться

«a» + «b» операция

Хотя конкатенация строк с помощью «+» читабельна, легко форматируется и прямолинейна, она считается плохой в Java.

Каждый раз, когда вы добавляете что-либо через '+' (String.concat ()), создается новая строка, копируется старое содержимое строки, добавляется новое содержимое, а старая строка удаляется. Чем больше Строка, тем больше времени требуется - нужно копировать больше, и получается больше мусора. Примечание: если вы просто объединяете несколько (скажем, 3,4) строк, а не построив строку с помощью цикла или просто написав какое-нибудь тестовое приложение, вы все равно можете придерживаться «+»

Используя StringBuilder

При выполнении обширных манипуляций со строкой (или добавлении через цикл), заменив «+» на StringBuilder .append, вероятно, рекомендуется. Промежуточные объекты, упомянутые в случае «+», не создаются во время вызова метода append () .

Также следует отметить оптимизацию в компиляторе Sun Java, который автоматически создает StringBuilders ( StringBuffers <5.0), когда видит конкатенации строк. Но это всего лишь компилятор Sun Java.

0
ответ дан 3 December 2019 в 13:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: