JavaString、StringBuilder、StringBuffer總結,stringbuilder
背景:
最近項目中需要用到伺服器模板和字串拼接技術。伺服器模板技術很多,JSP、Velocity、JDynamiTe等很多。字串拼接技術在Java中更簡單,StringBuilder、StringBuffer和重載的字串“+”操作。但是實際開發中,發現自己平時對Java的字串拼接的細節處理真的很差。
基礎:
字串操作主要問題在效率上,包括如下兩點:
(1)拼接效率。
(2)尋找、回溯效率。
凡是瞭解Java的都知道下面3點,而且這肯定是Java面試時必考的。
(1)StringBuilder是安全執行緒的;
(2)StringBuffer是線程不安全的;
(3)Java的String是final的,而且Java的String對象是放在字串常量池的。
其實在這之前,我對這幾點也只是“知其然而不知其所以然”,因為懶惰的關係,這三個類的源碼也沒看過,今天終於有時間看了下,順便總結下。
繼承體系
這個三個類的繼承體系如下:
(1)他們都實現了CharSequence介面,而且都是可以被序列化、被比較大小的。
(2)閱讀它們的源碼可以知道,他們在管理字串時,都使用的char數組。其實字串本身就是一種資料結構,和Stack、Queue一樣,只是String資料結構太常見了,才導致我們已經不把它當成一種資料結構了。
(3)StringBuilder和StringBuffer有一個共同的抽象父類AbstractStringBuilder,凡是對StringBuilder的方法調用,在StringBuilder內部都是調用的super方法,StringBuffer也一樣,只不過StringBuffer在自己的方法上加上了synchronized關鍵字。
append方法
我們對StringBuilder和StringBuffer調用最多的應該就是append方法了,append方法有很多重載方法,這些重載方法的實現思路都是一樣的,首先把傳入的參數轉換為char數組,然後把char數組和自己本身的數組進行合并,至於合并演算法就很簡單了(不過可以注意下合并後char數組的長度)。
不同類型的參數轉換為char數組的方式略有不同,Object類型調用String的靜態方法valueOf,基本類型的除了char和boolean外,都是調用的他們類型本身的getChars靜態方法。char添加比其他類型略快,不用調用getChars方法,boolean則直接添加一個[‘t’,’r’,’u’,’e’]或者[‘f’,’a’,’l’,’s’,’e’]字元數組。
所以從上面可以看出,調用append方法的時候如果可以直接append一個char或者char數組是最好的。
String
接著說下String,我們都知道JVM本身是對String對象有過最佳化的,而且實現了對String的操作符(+)的重載。JVM把String對象都放在常量池中,每次new對象的時候,都先去檢查常量池存不存在一個相同的字串序列,如果存在序列相同的就返回這個對象,如果不存在則建立一個新的。
有一個不負責任的笼統說法:JVM對String + 的重載其實是在編譯階段把+ 翻譯成了StringBuilder(JDK 1.4前是StringBuffer)。
比如:
String s = “a”;String m = s + “a” + “d”;
編譯後就是:
String s =”a”;String m = (new StringBuilder(s)).append(“a”).append(“d”).toString();
所以,根據這個規律我們在for迴圈中使用+操作效率是比較低的。
當然上面的情況是有特例的,畢竟Java編譯機制不會這麼簡單,比如當Java編譯器在編譯時間能確定字串的值時就不會遵循上面的編譯規則。
String s= “a”;String m = “s” + “x” + “e”;
這個時候m的值可以被確定,因為沒有變數參與運算,就可以直接賦值為sxe字串對象。
總結:
在構造StringBuilder的時候能確定序列長度,最好指定序列長度,這樣能減少append時的數組copy。
在使用StringBuilder的時候如果可以盡量使用append字元的方式,如:append(‘a’)比append(“a”)要好,而且不會產生字串a的對象。但是也不要特意這樣,比如append(‘a’).append(‘b’).append(‘c’)替代append(“abc”),那就有點畫蛇添足了。
其次,在append的參數中少用字串+操作,如果大量的使用字串+操作有的時候會失去使用StringBuilder的意義。比如
append(“a”+s+”d”);
應該拆分為
append(‘a’).append(s).append(‘d’);
這樣在大量迴圈中,能幫我們提高效率。
最後,關於字串的尋找和回溯操作,如果可以,我們可以直接使用索引的方式去截取字串也就是操作String類的API,這比在Java中使用Regex要好很多。