你會爲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