Использование String в цикле

Наткнулся на информацию, что при конкатенации строк на Java с помощью оператора +, например, в цикле for, каждый раз создается новый объект String, что приводит к потере памяти и увеличению времени работы программы.

Так ли это? И чем заменить сложение строк в цикле? Stringbilder грузит конкретно и точно приводит к повышенному расходу памяти


Ответы (2 шт):

Автор решения: Alex Rudenko

Заменить сложение строк чем-то более быстрым чем StringBuilder практически нечем, так как StringJoiner, String::join, и др. под капотом используют его, впрочем как и сам оператор +, о чём указано в документации:

The implementation of the string concatenation operator is left to the discretion of a Java compiler, as long as the compiler ultimately conforms to The Java™ Language Specification. For example, the javac compiler may implement the operator with StringBuffer, StringBuilder, or java.lang.invoke.StringConcatFactory depending on the JDK version.

Вольный перевод
Реализация оператора конкатенации строк оставляется на усмотрение Java компилятора, поскольку в конечном итоге компилятор соответствует спецификации языка Java JLS. Например, javac компилятор может реализовать этот оператор, используя StringBuffer, StringBuilder, или java.lang.invoke.StringConcatFactory в зависимости от версии JDK.

Также StringBuilder быстрее потокобезопасного StringBuffer за счёт отсутствия синхронизации, о чём также указано в документации, и поэтому именно он рекомендуется в однопоточном коде:

This class provides an API compatible with StringBuffer, but with no guarantee of synchronization. This class is designed for use as a drop-in replacement for StringBuffer in places where the string buffer was being used by a single thread (as is generally the case). Where possible, it is recommended that this class be used in preference to StringBuffer as it will be faster under most implementations.

Для увеличения производительности StringBuilder при работе с большими строками, может понадобиться установить его объём capacity при создании экземпляра:

StringBuilder large = new StringBuilder(Integer.MAX_VALUE); // максимальный размер
→ Ссылка
Автор решения: Stanislav Volodarskiy

Эксперимент String += против StringBuilder.

public class ConcatenationString {
    public static void main(String... args) {
        int n = Integer.parseInt(args[0]);
        String s = "";
        for (int i = 0; i < n; ++i) {
            s += " ";
        }
        System.out.println(s.length());
    }
}

public class ConcatenationStringBuilder {
    public static void main(String... args) {
        int n = Integer.parseInt(args[0]);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; ++i) {
            sb.append(" ");
        }
        String s = sb.toString();
        System.out.println(s.length());
    }
}

Сравниваем для тысячи символов:

$ javac -version
javac 1.8.0_45

$ javac ConcatenationString.java ConcatenationStringBuilder.java 

$ time java ConcatenationString 1000
1000

real  0m0.055s
user  0m0.052s
sys   0m0.004s

$ time java ConcatenationStringBuilder 1000
1000

real  0m0.055s
user  0m0.044s
sys   0m0.008s

Время равное, но это пока. Таблица:

         n         String   StringBuilder

         1         0.050с         0.048с
        10         0.053с         0.051с
       100         0.062с         0.050с
      1000         0.054с         0.051с
     10000         0.138с         0.062с
    100000         4.768с         0.055с
   1000000       177.833с         0.067с
  10000000                        0.188с
 100000000                        0.978с
1000000000                        9.284с

0.05с - это время старта Java, так что маленькие размеры не сравнишь просто так. Рост для String очень быстрый. Рост для StringBuilder медленный, в конце время работы пропорционально конечному размеру строки. На что тратится время? На выделение памяти, копирование пробелов, освобождение памяти и так без конца.

Что можно сказать точно:

  • Надо мерять время. Без чисел - всё болтовня.

  • На больших размерах (от 10000 символов) только StringBuilder.

  • На маленьких размерах - не понятно. Надо мерять время более тонко, тут померяно очень грубо.

  • Надо понимать что под капотом. Исходный код стандартной библиотеки есть, можно заглянуть.

  • Надо знать что такое О-большое. Очень сокращает время спора по некоторым вопросам. Этот ответ написан без отсылок к О-большому, потому так длинно.

→ Ссылка