你会为jdk-9字符串连接付出多少努力感到惊讶。第一个javac发出一个invokedynamic
而不是调用StringBuilder#append
。该invokedynamic将返回一个CallSite
并包含一个MethodHandle(实际上是一系列MethodHandle)。
因此,将实际完成的字符串连接的决定移至运行时。不利的一面是,你第一次连接将会变慢的字符串(对于相同类型的参数)。
然后有一系列的策略,你可以从连接字符串时选择(可以一个通过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
这是一个兽!
它采用包私有构造函数建立字符串(这是最快的):
/*
* Package private constructor which shares value array for speed.
*/
String(byte[] value, byte coder) {
this.value = value;
this.coder = coder;
}
首先这一战略将创建所谓的过滤器;这些基本上都是将传入参数转换为字符串值的方法句柄。正如人们所预料的,这些MethodHandles都存储在一个名为Stringifiers
类,在大多数情况下产生MethodHandle调用:
String.valueOf(YourInstance)
所以,如果你有,你想连接3个对象将有3个MethodHandles将委托到String.valueOf(YourObject)
这实际上意味着你已经将你的对象转换成了字符串。 这个班里面有一些我仍然无法理解的调整,像需要分开的类StringifierMost
(转换为仅字符串引用,浮动和双精度)和StringifierAny
。
由于MH_INLINE_SIZED_EXACT
表示字节数组被计算为精确的大小;有一种计算方法。
这样做的方式是通过StringConcatHelper#mixLen
中的方法获取输入参数的字符串化版本(References/float/double)。此时我们知道最终字符串的大小。那么,我们实际上并不知道它,我们有一个MethodHandle将会计算它。
String jdk-9还有一个值得一提的更改 - 增加了一个coder
字段。这是计算字符串的大小/相等/字符所需的。既然它需要大小,我们也需要计算它;这是通过StringConcatHelper#mixCoder
完成的。
正是在这一点上安全地委派MethodHandle,将创建乌尔数组:
@ForceInline
private static byte[] newArray(int length, byte coder) {
return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder);
}
如何每个元素追加?通过StringConcatHelper#prepend
中的方法。
只有现在我们需要调用需要一个字节的字符串的构造函数所需的所有细节。
所有这些操作(和许多其他我都跳过为简单起见)通过时发出的追加实际发生,将调用的MethodHandle处理。
在连接表达式时要小心重入。 – SLaks
最后我检查它是相当愚蠢的,强制'StringBuilder'反复重新分配。但是,这是Oracle的JDK专用的,查看得到的字节码,因此没有考虑JVM可能做的任何优化。我的规则是:99.999%的时间你不关心,当然;对于您关心的.001%,请使用分配足够大的字符串以显式处理总体结果。 –
除非你正在做的更多的字符串操作,而不仅仅是那一行,我同意T.J .: 99.999%的时间你不会看到任何区别。 JVM实际上将所有内存分配为线程本地的(直到它需要与另一个线程共享),iiuc,所以你的线程本地可能不会有任何好处。 – markspace