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