StringBuffer,StringBuilder以及String
他提到的問題也很有深度,然後思考了下,想評論來著。然而評論區太小,寫不下,所以單獨寫在這兒。
基本上可以當作快問快答來讀…
為什麼java中的string不以\0結尾?
- \0結尾在很大程度上要求程式設計師寫規範的程式碼,如果寫出了不規範的程式碼,那麼很容易就記憶體越界了。
- 另外,string的內部儲存是char[],而為了記憶體安全,java陣列本來就有一個length屬性,這時以\0結尾就是一個多餘的設計了。
- String的內部儲存也只能是char[]了,如果是其他的方式,比如通過native內部放一個c風格的陣列,那麼java程式碼中的char[]和string的轉換就要很多記憶體拷貝操作了。
- 而C語言設計成\0結尾,是為了減少抽象層,讓C語言更加貼近硬體
(在語言設計中,)字串的長度放哪裡,放到起始指標的位置,還是起始指標的前面 ?
- Java中,String的length也就是陣列的length,JLS也只是說明了arraylength位元組碼,沒有規定如何實現
- 不過Hot Spot的實現是,先元資料,再長度,再具體的內容(比如char[])
如果放前面,那麼字串起始指標和記憶體塊起始不一致怎麼解決
Java不存在這個問題,我覺得。元資料和length欄位都在實際陣列之前呢。Java中,訪問任何物件之前都要再多一次跳轉,跳過元資料(和length)。
字串拼接的時候把源串複製到目標串結尾,那麼目標串剩餘記憶體不夠怎麼辦,重新分配要多一次賦值,頻繁拼接效能有問題怎麼辦
- 這個問題比較核心,Java的String不可變設計導致了只能單獨複製一份來new String。
- 這個問題,編譯器、JVM很難優化,你把單個的字串拼接操作優化成StringBuilder的append,但是也扛不住我在for迴圈中頻繁append啊。而且每次new一個StringBuilder也是很大的負擔。
- 當然,也有優化的辦法,string相加的時候,string內部持有一個String陣列,在展示的時候,才拼成最終的String。但這個改動就比較大了。
要不要設計單獨的輔助類來解決字串拼接問題
那這個輔助類怎麼設計,要不要考慮執行緒安全
如果考慮執行緒安全的話,怎麼兼顧效能
現在看來是要一個輔助類的,比如StringBuilder(非執行緒安全)和StringBuffer(執行緒安全),但StringBuilder的執行緒安全也僅僅是每個方法前面加了synchronized而已啊。
然後說效能,我要append一個長度為1000的String,按理來說String不可變,我可以把String存到數組裡,build的時候再拼。
但是StringBuilder的實現還是一個一個char拷進StringBuilder,最後拼的時候,再拷了一次。多copy了一次啊。
說到這兒,@RednaxelaFX 也在回答的評論區提到了:
在Oracle JDK / OpenJDK的實現中,無論用StringBuffer還是StringBuilder去作為字串拼接的底層實現其實都不是最優的——它們倆都不是為append-only場景優化,而是為更通用的可變字串場景優化的。
然後我看看如何實現:
List<string>暫存一把最後需要的時候直接造個新string
我先去查了下,Apache Commons和Guava裡面居然都沒有這個實現,這個讓我很吃驚。
但是仔細一想,這個優化第三方庫確實很難做:
- List<string>先轉成char[],copy了一次。通過這個char[]來new String的時候,還得再copy一次(String和其他物件共享char[]的構造方法是包級別的,不是public的)
- 如果要做只能使用反射或者unsafe來強行呼叫了,這樣就和實現綁定了,不通用了。
所以現在看來,String的append操作,確實很難優化。要不將String實現搞複雜;要不上層自己寫StringBuilder來做。
參考資料: