Объединение строк было дорогостоящим делом в ранних версиях 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.

По умолчанию и наиболее производительный - MH_INLINE_SIZED_EXACT, что может привести к увеличению производительности в 3–4 раза. Вы можете переопределить Strategy в командной строке, определив свойство java.lang.invoke.stringConcat.

Стоит просто взглянуть на MH_INLINE_SIZED_EXACT: сочетает MethodHandles, чтобы увидеть, как теперь мы можем использовать MethodHandle для эффективной замены генерации кода.

Использованная литература :

Вопросы ? Предложения ? Комментарии ?

Что дальше? Подписывайтесь на меня на Medium, чтобы первым читать мои истории.