Конкатенация строк:concat() против оператора «+»

StackOverflow https://stackoverflow.com/questions/47605

  •  09-06-2019
  •  | 
  •  

Вопрос

Предполагая строки 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);
    }
}
Это было полезно?

Решение

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

Во-первых, есть небольшая разница в семантике.Если 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.

Другие советы

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

String a = b + c + d;

в

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

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

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

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

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

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

Большинство ответов здесь относятся к 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

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

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

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

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

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

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

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

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

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

    Например:

    String s = 10 + "Hello";
    

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

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

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

  2. Второе и главное отличие между + и конкат в том, что:

    Дело 1:Предположим, я объединяю одни и те же строки с помощью конкат оператор таким образом

    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);
    

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

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

Для полноты картины хотелось бы добавить, что определение оператора «+» можно найти в ДЖЛС SE8 15.18.1:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top