Конкатенация строк: concat () против оператора "+" - PullRequest
449 голосов
/ 06 сентября 2008

Предполагая строку 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 ]

522 голосов
/ 06 сентября 2008

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

Во-первых, есть небольшая разница в семантике. Если a равно null, то a.concat(b) выдает NullPointerException, но a+=b будет обрабатывать исходное значение a, как если бы оно было null. Кроме того, метод concat() принимает только значения 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. Вы можете видеть, что вы создаете массив символов (изменяя размер по мере необходимости), а затем выбрасываете его, когда создаете окончательный String. На практике распределение памяти происходит на удивление быстро.

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

86 голосов
/ 06 сентября 2008

Нияз правильно, но также стоит отметить, что специальный оператор + может быть преобразован в нечто более эффективное с помощью компилятора 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 при конкатенации, возможно, пустых строк, и использовать + в противном случае. Однако разница в производительности должна быть незначительной, и вам, вероятно, не стоит беспокоиться об этом.

44 голосов
/ 06 сентября 2008

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

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

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

23 голосов
/ 29 сентября 2017

Большинство ответов здесь с 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
22 голосов
/ 06 сентября 2008

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

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

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

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

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

21 голосов
/ 06 сентября 2008

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

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", зная, что это всегда приводит к одной и той же строке? Это могло бы избежать создания новой строки. Если вы не верите утверждению выше, проверьте себя.

6 голосов
/ 19 августа 2014

В принципе, есть два важных различия между + и 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.

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

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

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

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

3 голосов
/ 26 марта 2017

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

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

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

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

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

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

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

Таким образом, исходя из того, что «компилятор Java может использовать класс StringBuffer или аналогичный метод для сокращения», разные компиляторы могут создавать разные байт-коды.

2 голосов
/ 06 сентября 2008

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

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

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

2 голосов
/ 06 сентября 2008

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...