Строки интенсивно используются в программах на Java. Строки представляют собой последовательность символов. Для создания строки, обычно используется строковый литерал. Одна из особенностей строк в Java – они иммутабельны, то есть после создания они не могут быть изменены. Все операции над строками порождают новые строки, но никогда не меняют уже созданные. Это приводит к тому, что при интенсивных операциях над строками создаётся и высвобождается излишнее количество объектов с очень коротким временем жизни. Для решения этой проблемы были созданы StringBuffer и несколько позднее – StringBuilder.

Конкатенация с помощью оператора +

В самом простом случае можно объединить строки, воспользовавшись оператором сложения:

String hello = "Hello";
String world = " World";
System.out.println(hello + world);

Результатом выполнения данного кода будет вывод строки "Hello World" в консоль. Это самый простой и в большинстве случаев подходящий способ конкатенации строк в Java.

StringBuilder и StringBuffer, в отличие от класса String, созданы для того, чтобы иметь возможность изменять своё строковое значение. Оба эти класса инкапсулируют в себе массив байтов, в который добавляются новые значения. Когда длины массива недостаточно, выделяется память под массив большей размерности и все данные записываются в него.

Для чего используется StringBuilder

Обычно StringBuilder используется, если нужно создать строку в несколько этапов, используя несколько операций конкатенации, или же в цикле:

StringBuilder s = new StringBuilder();
s.append("Hello");
s.append(" World");
s.append(" in Java!");
System.out.println(s.toString());

С помощью метода StringBuilder.append() мы изменяем значение, хранящееся внутри экземпляра StringBuilder. Эта операция очень дешёвая в отличие создания новых объектов.

Давайте сравним производительность создания в цикле множества объектов String и использование StringBuilder. У нас есть два цикла:

long start = System.currentTimeMillis();
String s1 = "";
for (int i=0; i< 50000; i++) {
    s1 = s1 + "a";
}
System.out.println(System.currentTimeMillis() - start);

long start2 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i=0; i< 50000; i++) {
    sb.append("a");
}
System.out.println(System.currentTimeMillis() - start2);

В первом цикле в одну и ту же переменную типа String записывается вновь создаваемый объект строки, получаемый с помощью конкатенации двух строк (s1 + "a"). Это приводит к тому, что JVM постоянно создаёт новые объекты, захламляя память и тратя дополнительные ресурсы на освобождение памяти от мусора. Операции по выделению памяти под новый массив гораздо дешевле, поэтому в данном случае StringBuilder выигрывает в скорости по сравнению с String.

Для чего нужен StringBuffer

StringBuffer – это предшественник StringBuilder с теми же методами, но имеющий характерную особенность – методы StringBuffer синхронизированы, то есть класс потокобезопасен. StringBuilder же не является потокобезопасным, следовательно, его экземпляры должны использоваться только в одном потоке.

StringBuffer появился ещё в Java 1.0, а StringBuilder – в Java 5. В большинстве случаев, выбирая между этими двумя классами, рекомендуется использовать именно StringBuilder, если не требуется потокобезопасность.

Выводы

StringBuilder используется как эффективная промежуточная замена String в тех случаях, когда идёт интенсивная работа по объединению (замене, вставке, удалению) строк, а также при конкатенации строк в цикле. StringBuffer используется в тех же случаях, но является потокобезопасным (и несколько более медленным из-за синхронизированных методов).

Конкатенация строк в Java