Объединение строк: concat () против оператора "+"

Предполагая, что строки 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);
    }
}

возможный дубликат StringBuilder против конкатенации строк в toString () в Java

Greg Mattes 25.01.2012 04:45

Не уверен, что + можно декомпилировать.

Galen Nare 07.11.2013 00:50

Используйте javap для дизассемблирования файла класса Java.

Hot Licks 15.09.2014 19:59

Из-за `` неизменности '' вам, вероятно, следует использовать StringBuffer или StringBuilder- (поток небезопасен, поэтому быстрее, вместо этого

Ujjwal Singh 03.10.2015 06:39
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
523
5
967 080
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

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

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

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

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

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

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

«В настоящее время + реализован с использованием StringBuffer» False Это StringBuilder. StringBuffer - это потокобезопасный вариант StringBuilder.

Frederic Morin 06.09.2008 23:25

Раньше это был StringBuffer до java 1.5, так как это была версия, когда впервые был представлен StringBuilder.

ccpizza 15.11.2011 14:17

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

String a = b + c + d;

в

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

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

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

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

concat infact этого не делает. Я отредактировал свой пост декомпиляцией метода concat

shsteimer 06.09.2008 20:46

на самом деле это так. Посмотрите первые строки вашего кода concat. Проблема с concat в том, что он всегда генерирует новый String ()

Marcio Aguiar 06.09.2008 21:37

@MarcioAguiar: возможно, вы имеете в виду, что + всегда генерирует новый String - как вы говорите, у concat есть одно исключение, когда вы объединяете пустой String.

Blaisorblade 15.02.2014 14:02
Ответ принят как подходящий

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

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

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

@HyperLink Вы можете увидеть код, использующий javap -c в скомпилированном классе, который его использует. (О, как в ответе. Вам просто нужно интерпретировать разборку байт-кода, что не должно быть так сложно.)

Tom Hawtin - tackline 25.06.2013 21:58

Вы можете обратиться к Спецификация JVM, чтобы понять отдельные байт-коды. Материал, на который вы хотели бы сослаться, находится в главе 6. Немного неясно, но вы можете довольно легко понять суть.

Hot Licks 15.09.2014 20:03

Интересно, почему компилятор Java использует StringBuilder даже при соединении двух строк? Если String включал статические методы для объединения до четырех строк или всех строк в String[], код мог бы добавить до четырех строк с двумя выделениями объектов (результат String и его поддержка char[], ни одна из которых не является избыточной), и любое количество строк. с тремя выделениями (String[], результат String и резервный char[], причем только первое является избыточным). Как бы то ни было, использование StringBuilder потребует для в лучшем случае четырех выделений и потребует дважды копировать каждый символ.

supercat 02.12.2014 23:57

Это выражение a + = b. Разве это не значит: a = a + b?

most venerable sir 04.04.2015 23:22

@ user132522 Да. (Хотя с другими типами может быть задействовано приведение типов.)

Tom Hawtin - tackline 04.04.2015 23:27

Как это связано с конкатенацией? Вы имеете в виду, что a - строковый объект, а b - другого типа. Итак, b вынужден стать строковым объектом?

most venerable sir 04.04.2015 23:41

Это то, что вы имеете в виду под кастингом? Я на элементарном уровне. Я обычно использую слово «приведение» для преобразования между int и double (усечение).

most venerable sir 04.04.2015 23:43

@ user132522 Для таких типов, как short, результатом + будет int (потому что это такая странная вещь, которую делает Java). Таким образом, a = a + b; не будет компилироваться, поскольку вы не можете назначить int для short. a += b; будет молча возвращаться к short.

Tom Hawtin - tackline 04.04.2015 23:51

Таким образом, int = short + short не будет компилироваться. Но b / c сокращение приводит к сокращению результата int. В формате short + = short будет скомпилировано то же выражение. Я правильно понимаю?

most venerable sir 05.04.2015 00:05

Об этом StringBuilder лучше, чем concat: поправьте меня, если я ошибаюсь, но StringBuilder и другие ему подобные выделяют в 2 раза больше памяти для получения нового контента, который превышает потребность в емкости. Построение SQL-запросов я получал несколько раз ошибку, по этой причине в тестах, и я решил использовать «concat», было немного менее удобно, но память была хорошей.

EliuX 21.05.2015 18:53

Все изменилось с тех пор, как был создан этот ответ. Пожалуйста, прочтите мой ответ ниже.

Paweł Adamski 06.10.2017 16:25

Не могли бы вы подробнее рассказать о части "читы для компилятора байт-кода"?

NanoNova 11.07.2020 07:57

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

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

Я тестировал ваш код на java jdk1.8.0_241. Для меня код «a + b» дает оптимизированные результаты. С concat (): 203 мс и с «+»: 113 мс. Думаю, в предыдущем выпуске он не был оптимизирован.

Akki 28.04.2020 08:18

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

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

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

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

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

Я предполагаю, что под «этими временными операциями» вы подразумеваете использование анализа выхода для размещения объектов «кучи» в стеке, где это доказуемо правильно. Хотя в HotSpot есть escape-анализ (полезный для удаления некоторой синхронизации), я не верю, что на момент написания статьи u

Tom Hawtin - tackline 08.09.2008 23:48

Я провел аналогичный тест, как @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.

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

coolcfan 02.05.2012 09:46

@coolcfan: Когда + используется для двух строк, есть ли случаи, когда использование StringBuilder лучше, чем String.valueOf(s1).concat(s2)? Есть идеи, почему компиляторы не используют последний [или опускают вызов valueOf в тех случаях, когда известно, что s1 не равен нулю]?

supercat 22.09.2015 23:27

@supercat извини, я не знаю. Может быть, лучше всего на это ответят люди, стоящие за этим сахаром.

coolcfan 24.09.2015 06:22

Искать: invokedynamic StringConcatFactory

neoexpert 06.03.2021 00:11

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

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

    Например:

    String s = 10 + "Hello";
    

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

    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. Наслаждаться :)

Дорогой мой, Вы очень хорошо знаете, что любой строковый литерал рассматривается как сам объект String, который хранится в пуле String. Итак, в этом случае у нас есть 4 строковых литерала. Очевидно, что в пуле должно быть создано как минимум 4 объекта.

Deepak Sharma 20.08.2014 14:23

Я так не думаю: String s = "I"+"am"+"good"+"boy"; String s2 = "go".concat("od"); System.out.println(s2 == s2.intern()); печатает true, что означает, что "good" не был в пуле строк до вызова intern()

fabian 20.08.2014 16:13

Я говорю только об этой строке String s = «Я» + «am» + «good» + «boy»; В этом случае все 4 строковых литерала хранятся в пуле, поэтому в пуле должны быть созданы 4 объекта.

Deepak Sharma 20.08.2014 17:37

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

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

использование оператора + эквивалентно использованию StringBuilder (docs.oracle.com/javase/specs/jls/se8/html/…)

ihebiheb 22.01.2019 21:25

@ihebiheb или StringBuffer

NanoNova 07.07.2020 18:54

@ihebiheb "Реализация оператора конкатенации строк оставлена ​​на усмотрение компилятора Java, если компилятор в конечном итоге соответствует спецификации языка Java ™. Например, компилятор javac май реализует оператор с помощью StringBuffer, StringBuilder или java.lang.invoke.StringConcatFactory в зависимости от версии JDK (...) ". docs.oracle.com/en/java/javase/11/docs/api/java.base/java/la‌ ng /…

kasptom 09.08.2020 22:45

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

If only one operand expression is of type String, then string conversion (§5.1.11) is performed on the other operand to produce a string at run time.

The result of string concatenation is a reference to a String object that is the concatenation of the two operand strings. The characters of the left-hand operand precede the characters of the right-hand operand in the newly created string.

The String object is newly created (§12.5) unless the expression is a constant expression (§15.28).

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

An implementation may choose to perform conversion and concatenation in one step to avoid creating and then discarding an intermediate String object. To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.

For primitive types, an implementation may also optimize away the creation of a wrapper object by converting directly from a primitive type to a string.

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

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

Интересно, почему Java String никогда не включал статическую функцию для формирования строки путем объединения элементов String[]. Использование + для объединения 8 строк с использованием такой функции потребует создания и последующего отказа от String[8], но это будет единственный объект, который нужно будет построить заброшенным, в то время как использование StringBuilder потребует создания и отказа от экземпляра StringBuilder и по меньшей мере одного резервного хранилища char[] .

supercat 09.07.2019 19:57

@supercat Некоторые статические методы String.join() были добавлены в Java 8 в качестве быстрой синтаксической оболочки для класса java.util.StringJoiner.

Ti Strga 25.01.2020 00:43

@TiStrga: Изменилось ли обращение с + для использования таких функций?

supercat 25.01.2020 02:07

@supercat Это нарушит бинарную обратную совместимость, так что нет. Это было просто в ответ на ваш комментарий «почему String никогда не включал статическую функцию»: теперь есть такая функция является. Остальная часть вашего предложения (рефакторинг + для его использования), к сожалению, потребует большего, чем то, что разработчики Java готовы изменить.

Ti Strga 27.01.2020 19:49

@TiStrga: Есть ли способ, которым файл байт-кода Java может указывать: «Если функция X доступна, вызовите ее; в противном случае сделайте что-нибудь еще» таким образом, чтобы это могло быть разрешено в процессе загрузки класса? Создание кода с помощью статического метода, который может либо связываться со статическим методом Java, либо использовать построитель строк, если он недоступен, может показаться оптимальным решением.

supercat 27.01.2020 22:23

Не могли бы вы мне сказать, почему + быстрее StringBuilder в 2 раза?

NanoNova 11.07.2020 07:48

С Java 9. Все снова изменилось. Пожалуйста, обновите.

Olivier Grégoire 02.03.2021 14:03

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