您会惊讶于 jdk-9 字符串连接付出了多少努力。首先 javac 发出一个invokedynamic 而不是对StringBuilder#append 的调用。该 invokedynamic 将返回一个 CallSite ,其中包含一个 MethodHandle(实际上是一系列 MethodHandle)。
因此,对字符串连接实际执行什么操作的决定移至运行时。缺点是第一次连接字符串时会变慢(对于相同类型的参数)。
那么在连接一个String的时候有一系列的策略可以选择(可以通过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;
}
首先,此策略创建所谓的过滤器;这些基本上是将传入参数转换为字符串值的方法句柄。正如人们所预料的那样,这些 MethodHandle 存储在一个名为 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中的方法。
现在我们需要调用带字节的 String 的构造函数所需的所有细节。
所有这些操作(以及为简单起见跳过的许多其他操作)都是通过发出 MethodHandle 来处理的,该方法将在实际发生附加时调用。