Вы были бы удивлены, сколько усилий было уложено в конкатенацию jdk-9 String. Первый javac испускает invokedynamic
вместо вызова на StringBuilder#append
. Эта invokedynamic вернет CallSite
с содержит MethodHandle (это фактически серия MethodHandles).
Таким образом, решение того, что фактически выполняется для конкатенации строк, переносится во время выполнения. Недостатком является то, что при первом объединении строк, которые будут медленнее (для одного и того же типа аргументов).
Тогда существует ряд стратегий, которые вы можете выбрать из когда конкатенаций строки (вы можете изменить настройки по умолчанию один через java.lang.invoke.stringConcat
параметр):
private enum Strategy {
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder}.
*/
BC_SB,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but trying to estimate the required storage.
*/
BC_SB_SIZED,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but computing the required storage exactly.
*/
BC_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also tries to estimate the required storage.
*/
MH_SB_SIZED,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also estimate the required storage exactly.
*/
MH_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that constructs its own byte[] array from
* the arguments. It computes the required storage exactly.
*/
MH_INLINE_SIZED_EXACT
}
стратегия по умолчанию: MH_INLINE_SIZED_EXACT
который является зверем!
Он использует пакет частно-конструктор для создания String (который является самым быстрым):
/*
* Package private constructor which shares value array for speed.
*/
String(byte[] value, byte coder) {
this.value = value;
this.coder = coder;
}
Сначала эта стратегия создает так называемый фильтры; это в основном методы, которые преобразуют входящий параметр в значение String. Как и следовало ожидать, эти MethodHandles хранятся в классе под названием Stringifiers
, что в большинстве случаев производят MethodHandle, что вызывает:
String.valueOf(YourInstance)
Так что если у вас есть 3 объектов, которые вы хотите, чтобы сцепить будет 3 MethodHandles, которые делегируют до String.valueOf(YourObject)
, что фактически означает, что вы превратили свои объекты в строки. В этом классе есть определенные твики, которые я до сих пор не могу понять; например, необходимость иметь отдельные классы StringifierMost
(который преобразуется только в String только ссылки, float и double) и StringifierAny
.
Поскольку MH_INLINE_SIZED_EXACT
говорит, что массив байтов вычисляется до точного размера; есть способ вычислить это.
Способ, которым это делается с помощью методов в StringConcatHelper#mixLen
, которые принимают стробированную версию ваших входных параметров (Ссылки/float/double). На этом этапе мы знаем размер нашей финальной строки. Ну, мы действительно не знаем его, у нас есть MethodHandle, который его вычислит.
В String jdk-9 есть еще одно изменение, которое стоит упомянуть здесь - добавление поля coder
. Это необходимо для вычисления размера/равенства/charAt строки. Поскольку это необходимо для размера, нам также нужно вычислить его; это делается через StringConcatHelper#mixCoder
.
Это безопасно на данный момент делегировать MethodHandle, который будет создавать ур массив:
@ForceInline
private static byte[] newArray(int length, byte coder) {
return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder);
}
Как каждый элемент добавляется? С помощью методов в StringConcatHelper#prepend
.
И только теперь нам нужны все детали, необходимые для вызова этого конструктора String, который принимает байт.
Все эти операции (и многие другие, которые я пропускаюсь для простоты), обрабатываются с помощью испуская MethodHandle, который будет вызываться при приложенной на самом деле происходит.
Остерегайтесь повторного использования при конкатенации выражений. – SLaks
Последнее, что я проверил, было довольно глупым, заставляя 'StringBuilder' повторно перераспределять. Но это было специфично для JDK Oracle, рассматривая полученный байт-код, и поэтому не учитывали никакой оптимизации, которую может выполнять JVM. Мое правило было: 99,999% времени, которое вам все равно, конечно; для .001%, где вам небезразлично, используйте явный 'StringBuilder', выделенный достаточно большим для обработки итогового результата. –
Если вы не выполняете гораздо более строгие манипуляции, чем только одну строку, я согласен с T.J .: 99.999% времени, когда вы не увидите никакой разницы. JVM фактически будет распределять всю память как локальную для потока в любом случае (до тех пор, пока ей не понадобится поделиться с другим потоком), iiuc, поэтому ваш поток локально, вероятно, не принесет никакой пользы. – markspace