Объединение строк было дорогостоящим делом в ранних версиях Sun Java (до JDK1.4, если быть точным). Несмотря на то, что позже в JDK была реализована оптимизация компилятора конкатенации строк с использованием StringBuilder, все же класс String (а не просто конкатенация) является наиболее обсуждаемой темой во время собеседований или иным образом среди разработчиков Java.
Класс StringBuilder
class был представлен в JDK 1.5, и вместе с ним компилятор был оптимизирован для конкатенации строк, чтобы использовать StringBuilder вместо StringBuffer за кулисами (вы можете проверить байт-код, используя javap -c ). Итак, объединение строк с использованием оператора +:
String a = b + c + d;
был преобразован в
String a = new StringBuilder(b).append(c).append(d).toString();
Если оператор + автоматически конвертируется в построитель строк, почему он все еще существует?
Давайте возьмем пример, рассмотрим следующий класс:
public class StringConcatenation
{
public static void main(String[] args)
{
String result = "";
for (int i = 0; i < 1e6; i++)
{
result += "some data";
}
System.out.println(result);
}
}
это фактически преобразуется в это компиляторами до JDK 8, как показано ниже:
public class StringConcatenation
{
public static void main(String[] args)
{
String result = "";
for (int i = 0; i < 1e6; i++)
{
StringBuilder tmp = new StringBuilder();
tmp.append(result);
tmp.append("some more data");
result = tmp.toString();
}
System.out.println(result);
}
}
Из-за создания 1 миллиона объектов и вероятного GC это имеет низкую производительность. По этой причине рекомендуется избегать оптимизации компилятора и напрямую использовать класс String Builder следующим образом:
public class StringConcatenation
{
public static void main(String[] args)
{
StringBuilder result = new StringBuilder((int)1e6);
for (int i = 0; i < 1e6; i++)
{
result.append("some more data");
}
System.out.println(result.toString());
}
}
Java 9 приносит еще одну оптимизацию
Начиная с Java 9 ( Java Enhancement Proposal 280 или JEP 280), вся последовательность добавления StringBuilder была заменена простой invokedynamic ( Подробнее об этом в следующем разделе. ) вызов java.lang.invoke.StringConcatFactory, который примет значения, требующие объединения.
До Java 7 в JVM было только четыре типа вызова методов: invokevirtual для вызова обычных методов класса, invokestatic для вызова статических методов, invokeinterface для вызова методы интерфейса и invokespecial для вызова конструкторов или частных методов.
Зачем нужна динамическая реализация вместо StringBuilder?
Основная мотивация изменения заключается в изменении стратегии конкатенации без изменения байт-кода, что позволяет полностью избежать перекомпиляции.
Что такое InvokeDynamic (также называемый Indy)?
В voke Dy имя opcode было добавлено как часть JSR 292 (первая версия Java 7) для поддержки эффективных и гибких выполнение вызовов методов при отсутствии информации о статическом типе.
JDK в основном определяет спецификацию байт-кода во время компиляции, а реализация этой спецификации выбирается во время выполнения. Давайте рассмотрим пример. Предположим, мы хотим объединить «Я есть» и «Грут».
- Создается сигнатура функции, например concat (String, String) - ›String
- Аргументы против вышеуказанной функции: «Я» и «Грут».
- Вызывается метод начальной загрузки makeConcatWithConstants с указанной выше сигнатурой функции, аргументами и несколькими другими параметрами, необходимыми для динамичности (среди них стратегия подробнее об этом в следующем разделе), который возвращает объект CallSite.
- Этот объект CallSite инкапсулирует серию MethodHandles, которая указывает на фактическую целевую реализацию для этой сигнатуры функции.
- Теперь эта сгенерированная функция используется для возврата объединенной строки «I am Groot».
Стратегии конкатенации строк в Java 9+
StringConcatFactory
предлагает различные стратегии для генерации CallSite
генератора, разделенного на байт-код, с использованием ASM и метода на основе MethodHandle.
BC_SB
: генерировать байт-код, эквивалентный тому, чтоjavac
генерирует в Java 8.BC_SB_SIZED
: генерировать байт-код, эквивалентный тому, чтоjavac
, но попытаться оценить начальный размерStringBuilder
.BC_SB_SIZED_EXACT
: генерировать байт-код, эквивалентный тому, чтоjavac
, но вычислить точный размерStringBuilder
.MH_SB_SIZED
: объединяет MethodHandles, который в итоге вызываетStringBuilder
с предполагаемым начальным размером.MH_SB_SIZED_EXACT
: объединяет MethodHandles, что приводит к вызовуStringBuilder
с точным размером.MH_INLINE_SIZED_EXACT
: объединяет MethodHandles, который создает непосредственно String с байтом точного размера [] без копии.
По умолчанию и наиболее производительный - MH_INLINE_SIZED_EXACT
, что может привести к увеличению производительности в 3–4 раза. Вы можете переопределить Strategy
в командной строке, определив свойство java.lang.invoke.stringConcat
.
Стоит просто взглянуть на MH_INLINE_SIZED_EXACT
: сочетает MethodHandles, чтобы увидеть, как теперь мы можем использовать MethodHandle для эффективной замены генерации кода.
Использованная литература :
- Https://howtodoinjava.com/java9/compact-strings/
- Http://cr.openjdk.java.net/~ntv/talks/eclipseSummit16/indyunderTheHood.pdf
- Https://arnaudroger.github.io/blog/2017/06/14/CompactStrings.html
- Https://medium.com/better-programming/top-5-new-features-expected-in-java-14-82c0d85b295e
- Https://www.baeldung.com/java-invoke-dynamic
- Https://www.baeldung.com/java-string-concatenation-invoke-dynamic
Вопросы ? Предложения ? Комментарии ?
Что дальше? Подписывайтесь на меня на Medium, чтобы первым читать мои истории.