Рубрики
Без рубрики

Компактные строки в Java 9

Откройте для себя компактные строки – новую функцию Java 9.

Автор оригинала: baeldung.

1. Обзор

Строки в Java внутренне представлены символом char [] , содержащим символы Строки . И каждый символ состоит из 2 байтов, потому что Java внутренне использует UTF-16.

Например, если Строка содержит слово на английском языке, начальные 8 бит будут равны 0 для каждого символа , поскольку символ ASCII может быть представлен одним байтом.

Для представления многих символов требуется 16 бит, но статистически большинству требуется только 8 бит — латинское представление 1 символа. Таким образом, есть возможности для повышения потребления памяти и производительности.

Что также важно, так это то, что String s обычно занимают большую часть пространства кучи JVM. И из-за того, как они хранятся в JVM, в большинстве случаев /Строка экземпляр может занимать двойное пространство , которое ему действительно нужно .

В этой статье мы обсудим опцию Сжатой строки, представленную в JDK 6, и новую компактную строку, недавно представленную в JDK9. Оба они были разработаны для оптимизации потребления памяти строками в JMV.

2. Сжатая строка – Java 6

В выпуске производительности JDK 6 с обновлением 21 появилась новая опция виртуальной машины:

-XX:+UseCompressedStrings

Когда эта опция включена, Строки сохраняются как байт [] вместо char []– , что позволяет сэкономить много памяти. Однако в конечном итоге эта опция была удалена в JDK 7, главным образом потому, что она имела некоторые непредвиденные последствия для производительности.

3. Компактная строка – Java 9

Java 9 привнесла концепцию компактных строк ba ck.

Это означает, что всякий раз, когда мы создаем Строку , если все символы Строки могут быть представлены с использованием представления byte — LATIN-1, внутри будет использоваться массив байтов , так что для одного символа задается один байт.

В других случаях, если для представления какого-либо символа требуется более 8 бит, все символы сохраняются с использованием двух байтов для каждого представления — UTF-16.

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

Теперь вопрос в том, как будут работать все операции String ? Как он будет различать представления LATIN-1 и UTF-16?

Что ж, для решения этой проблемы во внутреннюю реализацию Строка . У нас есть последнее поле кодер , который сохраняет эту информацию.

3.1. Реализация строк в Java 9

До сих пор строка | хранилась как символ[] :

private final char[] value;

Отныне это будет байт[]:

private final byte[] value;

Переменная кодер :

private final byte coder;

Где кодер может быть:

static final byte LATIN1 = 0;
static final byte UTF16 = 1;

Большинство операций String теперь проверяют кодер и отправляют его в конкретную реализацию:

public int indexOf(int ch, int fromIndex) {
    return isLatin1() 
      ? StringLatin1.indexOf(value, ch, fromIndex) 
      : StringUTF16.indexOf(value, ch, fromIndex);
}  

private boolean isLatin1() {
    return COMPACT_STRINGS && coder == LATIN1;
}

Когда вся необходимая JVM информация готова и доступна, опция Компактная строка Виртуальная машина включена по умолчанию. Чтобы отключить его, мы можем использовать:

+XX:-CompactStrings

3.2. Как Работает кодер

В реализации класса Java 9 String длина вычисляется как:

public int length() {
    return value.length >> coder;
}

Если Строка содержит только латинское-1, значение кодера будет равно 0, поэтому длина Строки будет такой же, как длина массива байтов.

В других случаях, если Строка находится в представлении UTF-16, значение кодера будет равно 1, и, следовательно, длина будет вдвое меньше размера фактического массива байтов.

Обратите внимание, что все изменения, внесенные в Compact Строка, находятся во внутренней реализации Строка класса и полностью прозрачны для разработчиков, использующих Строка .

4. Компактные строки по сравнению со Сжатыми строками

В случае JDK 6 Со сжатыми Строками основная проблема заключалась в том, что конструктор String принимал в качестве аргумента только char [] . В дополнение к этому многие операции String зависели от представления char [] , а не от массива байтов. Из-за этого пришлось много распаковывать, что сказалось на производительности.

В то время как в случае компактной строки сохранение дополнительного поля “кодер” также может увеличить накладные расходы. Чтобы снизить стоимость кодера и распаковки байта s в символ s (в случае представления UTF-16), некоторые методы интринсифицированы и код ASM, сгенерированный JIT-компилятором, также был улучшен.

Это изменение привело к некоторым противоречивым результатам. Латинский-1 indexOf(строка) вызывает встроенный метод, в то время как indexOf(символ) этого не делает. В случае UTF-16 оба этих метода вызывают внутренний метод. Эта проблема затрагивает только латинскую-1 Строку и будет исправлена в будущих выпусках.

Таким образом, компактные Строки лучше, чем сжатые Строки с точки зрения производительности.

Чтобы выяснить, сколько памяти сохраняется с помощью компактных строк , были проанализированы различные дампы кучи Java-приложений. И, хотя результаты сильно зависели от конкретных приложений, общие улучшения почти всегда были значительными.

4.1. Разница в производительности

Давайте рассмотрим очень простой пример разницы в производительности между включением и отключением компактных Строк:

long startTime = System.currentTimeMillis();
 
List strings = IntStream.rangeClosed(1, 10_000_000)
  .mapToObj(Integer::toString) 
  .collect(toList());
 
long totalTime = System.currentTimeMillis() - startTime;
System.out.println(
  "Generated " + strings.size() + " strings in " + totalTime + " ms.");

startTime = System.currentTimeMillis();
 
String appended = (String) strings.stream()
  .limit(100_000)
  .reduce("", (l, r) -> l.toString() + r.toString());
 
totalTime = System.currentTimeMillis() - startTime;
System.out.println("Created string of length " + appended.length() 
  + " in " + totalTime + " ms.");

Здесь мы создаем 10 миллионов Строк s, а затем добавляем их наивным способом. Когда мы запускаем этот код (компактные строки включены по умолчанию), мы получаем вывод:

Generated 10000000 strings in 854 ms.
Created string of length 488895 in 5130 ms.

Аналогично, если мы запустим его, отключив Компактные строки с помощью опции: -XX:-CompactStrings , вывод будет:

Generated 10000000 strings in 936 ms.
Created string of length 488895 in 9727 ms.

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

5. Заключение

В этом уроке мы рассмотрели попытки оптимизировать производительность и потребление памяти в JVM – путем эффективного хранения String s в памяти.

Как всегда, весь код доступен на Github .