Конкатенация строк: оператор concat() vs "+"

Предполагая, что строки a и b:

a += b
a = a.concat(b)

Под капотом они одно и то же?

Здесь concat декомпилируется как ссылка. Я хотел бы также декомпилировать оператор +, чтобы увидеть, что это делает.

public String concat(String s) {

    int i = s.length();
    if (i == 0) {
        return this;
    }
    else {
        char ac[] = new char[count + i];
        getChars(0, count, ac, 0);
        s.getChars(0, i, ac, count);
        return new String(0, count + i, ac);
    }
}

Ответ 1

Нет, не совсем.

Во-первых, есть небольшая разница в семантике. Если a - null, то a.concat(b) выбрасывает NullPointerException, но a+=b будет обрабатывать исходное значение a, как если бы оно было null. Кроме того, метод concat() принимает только значения String, в то время как оператор + молча преобразует аргумент в String (используя метод toString() для объектов). Таким образом, метод concat() более строг в том, что он принимает.

Чтобы посмотреть под капотом, напишите простой класс с a += b;

public class Concat {
    String cat(String a, String b) {
        a += b;
        return a;
    }
}

Теперь разоберите javap -c (включен в Sun JDK). Вы должны увидеть список, содержащий:

java.lang.String cat(java.lang.String, java.lang.String);
  Code:
   0:   new     #2; //class java/lang/StringBuilder
   3:   dup
   4:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   7:   aload_1
   8:   invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   11:  aload_2
   12:  invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   15:  invokevirtual   #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/    String;
   18:  astore_1
   19:  aload_1
   20:  areturn

Итак, a += b является эквивалентом

a = new StringBuilder()
    .append(a)
    .append(b)
    .toString();

Метод concat должен быть быстрее. Однако с большим количеством строк метод StringBuilder выигрывает, по крайней мере, с точки зрения производительности.

Исходный код String и StringBuilder (и его пакет-частный базовый класс) доступен в src.zip Sun JDK. Вы можете увидеть, что вы создаете массив char (при необходимости изменяя размер) и затем отбрасывая его, когда вы создаете окончательный String. На практике распределение памяти происходит на удивление быстро.

Обновление: Как отмечает Павел Адамски, производительность изменилась в более поздней версии HotSpot. javac по-прежнему производит точно такой же код, но компилятор байт-кода компилирует. Простое тестирование полностью терпит неудачу, потому что весь код кода выброшен. Суммирование System.identityHashCode (не String.hashCode) показывает, что код StringBuffer имеет небольшое преимущество. Возможны изменения при выпуске следующего обновления или при использовании другой JVM. Из @lukaseder, список свойств JVM HotSpot.

Ответ 2

Niyaz является правильным, но также стоит отметить, что специальный + оператор может быть преобразован в нечто более эффективное с помощью компилятора Java. Java имеет класс StringBuilder, который представляет собой потокобезопасную, изменяемую строку. Когда вы выполняете связку конкатенаций String, компилятор Java беззвучно преобразует

String a = b + c + d;

в

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

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

Однако метод concat более эффективен при объединении пустой строки в существующую строку. В этом случае JVM не нужно создавать новый объект String и может просто вернуть существующий. См. документацию concat, чтобы подтвердить это.

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

Ответ 3

Я провел аналогичный тест, как @marcio, но вместо этого:

String c = a;
for (long i = 0; i < 100000L; i++) {
    c = c.concat(b); // make sure javac cannot skip the loop
    // using c += b for the alternative
}

Просто для хорошей меры я также добавил StringBuilder.append(). Каждый тест проводился 10 раз, с 100 к повторениями для каждого прогона. Вот результаты:

  • StringBuilder выигрывает руки. Для большинства прогонов результат был равен 0, а самый длинный - 16 мс.
  • a += b занимает около 40000 мс (40 секунд) для каждого прогона.
  • concat требуется только 10000 мс (10 с) за прогон.

Я не декомпилировал класс, чтобы видеть внутренности или запускать его через профайлер, но я подозреваю, что a += b большую часть времени тратит на создание новых объектов StringBuilder, а затем преобразовывает их обратно в String.

Ответ 4

Том правильно описывает то, что делает оператор+. Он создает временный StringBuilder, добавляет части и заканчивается toString().

Тем не менее, все ответы до сих пор игнорируют эффекты оптимизации среды выполнения HotSpot. В частности, эти временные операции распознаются как общий шаблон и заменяются более эффективным машинным кодом во время выполнения.

@marcio: вы создали микро-бенчмарк; с современной JVM это недействительный способ профилировать код.

Причина оптимизации времени выполнения заключается в том, что многие из этих различий в коде - даже включая создание объектов - полностью различаются после того, как HotSpot начинает работать. Единственный способ узнать наверняка - это профилирование вашего кода in situ.

Наконец, все эти методы на самом деле невероятно быстрые. Это может быть случай преждевременной оптимизации. Если у вас есть код, который объединяет строки много, способ получить максимальную скорость, вероятно, не имеет никакого отношения к тому, какие операторы вы выберете, а вместо того алгоритма, который вы используете!

Ответ 5

Большинство ответов здесь приведены с 2008 года. Похоже, что с течением времени ситуация изменилась. Мои последние тесты, сделанные с JMH, показывают, что на Java 8 + примерно в два раза быстрее, чем concat.

Мой ориентир:

@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
public class StringConcatenation {

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State2 {
        public String a = "abc";
        public String b = "xyz";
    }

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State3 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
    }


    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State4 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
        public String d = "[email protected]#";
    }

    @Benchmark
    public void plus_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b);
    }

    @Benchmark
    public void plus_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c);
    }

    @Benchmark
    public void plus_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c+state.d);
    }

    @Benchmark
    public void stringbuilder_2(State2 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).toString());
    }

    @Benchmark
    public void stringbuilder_3(State3 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).toString());
    }

    @Benchmark
    public void stringbuilder_4(State4 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).append(state.d).toString());
    }

    @Benchmark
    public void concat_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b));
    }

    @Benchmark
    public void concat_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c)));
    }


    @Benchmark
    public void concat_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c.concat(state.d))));
    }
}

Результаты:

Benchmark                             Mode  Cnt         Score         Error  Units
StringConcatenation.concat_2         thrpt   50  24908871.258 ± 1011269.986  ops/s
StringConcatenation.concat_3         thrpt   50  14228193.918 ±  466892.616  ops/s
StringConcatenation.concat_4         thrpt   50   9845069.776 ±  350532.591  ops/s
StringConcatenation.plus_2           thrpt   50  38999662.292 ± 8107397.316  ops/s
StringConcatenation.plus_3           thrpt   50  34985722.222 ± 5442660.250  ops/s
StringConcatenation.plus_4           thrpt   50  31910376.337 ± 2861001.162  ops/s
StringConcatenation.stringbuilder_2  thrpt   50  40472888.230 ± 9011210.632  ops/s
StringConcatenation.stringbuilder_3  thrpt   50  33902151.616 ± 5449026.680  ops/s
StringConcatenation.stringbuilder_4  thrpt   50  29220479.267 ± 3435315.681  ops/s

Ответ 6

Как насчет простого тестирования? Используется следующий код:

long start = System.currentTimeMillis();

String a = "a";

String b = "b";

for (int i = 0; i < 10000000; i++) { //ten million times
     String c = a.concat(b);
}

long end = System.currentTimeMillis();

System.out.println(end - start);
  • Версия "a + b" выполнена в 2500 мс.
  • a.concat(b) выполнен в 1200 мс.

Протестировано несколько раз. Выполнение версии concat() занимало в среднем половину времени.

Этот результат удивил меня, потому что метод concat() всегда создает новую строку (она возвращает "new String(result)". Хорошо известно, что:

String a = new String("a") // more than 20 times slower than String a = "a"

Почему компилятор не смог оптимизировать создание строки в коде "a + b", зная, что это всегда приводило к той же строке? Это могло бы избежать создания новой строки. Если вы не верите приведенному выше заявлению, проверьте себя.

Ответ 7

В принципе, существуют два важных различия между + и concat.

  • Если вы используете метод concat, тогда вы сможете конкатенировать строки, в то время как в случае оператора + вы можете также объединить строку с любым типом данных.

    Пример:

    String s = 10 + "Hello";
    

    В этом случае вывод должен быть 10Hello.

    String s = "I";
    String s1 = s.concat("am").concat("good").concat("boy");
    System.out.println(s1);
    

    В приведенном выше случае вам необходимо указать две строки.

  • Второе и основное различие между + и concat заключается в следующем:

    Случай 1: Предположим, что я объединяю те же строки с оператором concat таким образом

    String s="I";
    String s1=s.concat("am").concat("good").concat("boy");
    System.out.println(s1);
    

    В этом случае общее количество объектов, созданных в пуле, равно 7:

    I
    am
    good
    boy
    Iam
    Iamgood
    Iamgoodboy
    

    Случай 2:

    Теперь я собираюсь конкатенировать те же строки с помощью оператора +

    String s="I"+"am"+"good"+"boy";
    System.out.println(s);
    

    В приведенном выше случае общее количество созданных объектов составляет всего 5.

    Фактически, когда мы объединяем строки с помощью оператора +, то он поддерживает класс StringBuffer для выполнения той же задачи следующим образом: -

    StringBuffer sb = new StringBuffer("I");
    sb.append("am");
    sb.append("good");
    sb.append("boy");
    System.out.println(sb);
    

    Таким образом, он создаст только пять объектов.

Итак, ребята, это основные различия между + и concat. Наслаждайтесь:)

Ответ 8

Для полноты я хотел добавить, что определение оператора "+" можно найти в JLS SE8 15.18.1:

Если только одно выражение операнда имеет тип String, тогда строка преобразование (§5.1.11) выполняется на другом операнде для создания строка во время выполнения.

Результат конкатенации строк - это ссылка на объект String это конкатенация двух операндных строк. Персонажи левого операнда предшествуют символам правого операнд во вновь созданной строке.

Объект String только что создан (§12.5), если выражение не является постоянное выражение (§15.28).

О реализации JLS говорит следующее:

Реализация может выбрать выполнение преобразования и конкатенации в один шаг, чтобы избежать создания и затем отбрасывания промежуточного Строковый объект. Чтобы увеличить производительность повторяющейся строки concatenation, компилятор Java может использовать класс StringBuffer или аналогичная техника для уменьшения количества промежуточных объектов String которые создаются путем оценки выражения.

Для примитивных типов реализация также может оптимизировать создание объекта-обертки путем преобразования непосредственно из примитива введите строку.

Поэтому, судя по "компилятору Java, можно использовать класс StringBuffer или подобный метод для уменьшения", разные компиляторы могут создавать разные байт-коды.

Ответ 9

Оператор + может работать между строкой и строкой, значением типа w980 > , integer, double или float. Он просто преобразует значение в его строковое представление перед конкатенацией.

Оператор concat может выполняться только со строками и с ними. Он проверяет совместимость типов данных и выдает ошибку, если они не совпадают.

Кроме того, предоставленный вами код делает то же самое.

Ответ 10

Я так не думаю.

a.concat(b) реализован в String, и я думаю, что реализация не сильно изменилась с ранних машин java. Реализация операции + зависит от версии Java и компилятора. В настоящее время + реализован с использованием StringBuffer чтобы сделать операцию максимально быстрой. Возможно, в будущем это изменится. В более ранних версиях java + работа со строками была намного медленнее, поскольку она давала промежуточные результаты.

Я предполагаю, что += реализован с использованием + и аналогичным образом оптимизирован.

Ответ 11

При использовании + скорость уменьшается по мере увеличения длины строки, но при использовании concat скорость более стабильна, и лучший вариант - использовать класс StringBuilder, который имеет стабильную скорость, чтобы сделать это.

Я думаю, вы можете понять, почему. Но наилучшим способом создания длинных строк является использование StringBuilder() и append(), либо скорость будет неприемлемой.

Ответ 12

Для повышения производительности лучше всего выбрать StringBuffer.

Самый быстрый способ конкатенации двух строк с помощью оператора +.

String str = "Java";
str = str + "Tutorial";

Компилятор переводит этот код как:

String longStr = "Java";
StringBuffer tmpSb = new StringBuffer(longStr);
tmpSb.append("Tutorial");
longStr = tmpSb.toString();

Основной причиной снижения производительности является создание множества временных объектов String из-за неизменности String. Просто помните, что конкатенация String с помощью оператора + также преобразуется в соответствующий вызов StringBuffer или StringBuilder в зависимости от того, какую версию Java вы используете, потому что StringBuilder доступен только с Java 1.5. Поэтому лучше всего использовать StringBuffer.

String str = new StringBuffer().append("first").append("second").append("third").toString();

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

Rj