Конкатенация строк: 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);
    }
}

11 ответов


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

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

To посмотрите под капот, напишите простой класс с 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.молния Солнца JDK. Вы можете видеть, что вы создаете массив символов (изменение размера по мере необходимости), а затем отбрасываете его при создании final String. На практике выделение памяти происходит удивительно быстро.

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


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

String a = b + c + d;

на

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

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

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

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


я провел аналогичный тест как @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 раз, с повторениями 100k для каждого запуска. Вот результаты:

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

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


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

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

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

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

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


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

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" версия выполнена в 2500ms.
  • на a.concat(b) выполнена в 1200ms.

проверено несколько раз. The concat() исполнение версии заняло в среднем половину времени.

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

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

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


большинство ответов здесь с 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 = "!@#";
    }

    @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

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

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

    Например:

    String s = 10 + "Hello";
    

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

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

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

  2. второе и главное различие между + и функция 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.

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

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

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

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


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

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

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

строковый объект создается заново (§12.5), если выражение не является константным выражением (§15.28).

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

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

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

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


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

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

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


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

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

думаю, что += реализуется с помощью + и аналогично оптимизирован.


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

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