Конкатенация строк в Java — быстрее?

15 января 2010 г.

За последнее время появилось несколько постов связанных с измерениями скорости работы со строками в Java. Хотелось поделиться полученным несколько месяцев назад методом «ускорения» приложения, а именно, ускорением конкатенации строк. Это обычно одна из наиболее используемых строковых операций в общего рода приложениях и ее улучшение часто повышает производительность в целом.

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

Как можно соединить одну или несколько строк?

Способ #1: плюс

1
String result = a + b;

Самый очевидный для многих начинающих программистов, особенно перешедших в Java из других ЯП, где строки тоже можно «плюсовать».
На самом деле за этой конструкцией скрывается достаточно емкая операция. Тут создается новый StringBuilder и в него добавляются все полученные на вход строки. И не забываем про .toString(), на него тоже идет время.
Кстати, эта скрытая возможность может смущать начинающих кодеров. Компилируем код javac версии 1.5+ и запускаем на JVM без StringBuilder-а, например, BlackBerry, J2ME или других урезанных системах и получаем ошибку ClassNotFoundException, хотя явно нигде этот класс не используется.

Способ #2: String.concat(String)

1
String result = a.concat(b);

Использование «родных» методов String-а даст ощутимый прирост в производительности — они писались с расчетом на скорость и часто дают хорошие результаты. Впрочем, почему тогда не заменять одинарный “+” на .concat хотя бы на этапе компиляции?

Способ #3: StringBuffer

1
2
3
4
StringBuffer sb = new StringBuffer();
sb.append(a);
sb.append(b);
String result = sb.toString;

Хороший способ для конкатенации многих строк, допустим, в цикле. Впрочем, эту конструкцию можно еще оптимизировать:

1
String result = new StringBuffer(a).append(b).toString();

и за счет того, что в конструктор первая строка уже будет передаваться, этот способ будет работать немного быстрее.

Способ #4: StringBuilder

Использование аналогично предыдущему пункту, вместо StringBuffer — StringBuilder. Будет работать быстрее из-за не-синхронизированных методов последнего. Правда, проверка на java6 показала, что время почти не отличается, хотя на 5-й — почти на треть. Оптимизировали значит.

Способ #5: Re-using variables

Часто в советах оптимизации проскакивает — не плодите сущностей (с), то-есть используйте уже созданные экземпляры классов. Можем применить это для нашей конкатенации:

1
2
3
4
private / static private StringBuilder staticSb = new StringBuilder();
...
staticSb.delete(0, staticSb.length());
String result = staticSb.append(a).append(b).toString;

Такой метод очищения буфера, кстати, быстрее чем, например, StringBuilder.setLength(0);

Результаты

Тестирование проводилось на jre1.6.0_21 не так быстро как хотелось бы. Так же соединялись 3 и больше подстрок. Лучшие результаты выделены цветом.
Цифры в наносекундах за одно исполнение, исполнений каждого блока было по 100000. Выполнялось 5 измерений, лучшее и худшее отбрасывалось.

Аргументы виртуальной машины: -Xmx1024M -Xms1024M для уменьшения влияния GarbageCollector-а на время.

2345
a+b+c...1109153417492751
a.concat(b)…39576812951885
StringBuffer1263164325042740
StringBuilder1042148223712397
Pre-allocated StringBuilder552617785967

Исходный код программы-мерялки:

Выводы

  • Выбор метода зависит от количества строк
  • Для соединения двух строк лучше использовать родной String.concat()
  • Для часто вызываемого метода, в котором будут соединяться больше двух строк, лучше использовать последний метод с очищением буфера и повторного его использования. Для thread-safety можно делать это поле ThreadLocal, например. Или вообще не-статичным членом класса.
  • В ходе экспериментов так же выяснилось, что одиночные a+b(+c) лучше заменять на a.concat, при большем количестве — StringBuilder.

Впрочем, не забываем, что Premature optimization is the root of all evil

Теги:
рубрика Java