Вопрос-ответ

String concatenation: concat() vs "+" operator

Конкатенация строк: 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);
}
}
Переведено автоматически
Ответ 1

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

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

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

Ответ 2

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

Ответ 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 с) за запуск.

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

Ответ 4

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