java連接字符串有多種方式,比如+操作符, StringBuilder.append 方法,這些方法各有什么優(yōu)劣(可以適當說(shuō)明各種方式的實(shí)現細節)?
按照高效的原則,那么java中字符串連接的最佳實(shí)踐是什么?
有關(guān)字符串處理,都有哪些其他的最佳實(shí)踐?
廢話(huà)不多說(shuō),直接開(kāi)始, 環(huán)境如下:
JDK版本: 1.8.0_65
CPU: i7 4790`
內存: 16G
直接使用 + 拼接
看下面的代碼:
StringBuilder 和 StringBuffer 在 大部分情況下 比后者都低,這里說(shuō)是 大部分情況下 ,我們用javap工具對上面代碼生成的字節碼進(jìn)行反編譯看看在編譯器對這段代碼做了什么。從反編譯的結果來(lái)看,實(shí)際上對字符串使用 + 操作符進(jìn)行拼接,編譯器會(huì )在編譯階段把代碼優(yōu)化成使用 StringBuilder 類(lèi),并調用 append 方法進(jìn)行字符串拼接,最后調用 toString 方法,這樣看來(lái)是否可以認為在一般情況下 其實(shí)直接使用+,反正編譯器也會(huì )幫我優(yōu)化為使用StringBuilder ?
StringBuilder 源碼分析
答案自然是 不可以 的,原因就在于 StringBuilder 這個(gè)類(lèi)它內部做了些什么時(shí)。
我們看一看 StringBuilder 類(lèi)的構造器
StringBuilder 提供了4個(gè)默認的構造器, 除了無(wú)參構造函數外,還提供了另外3個(gè)重載版本,而內部都調用父類(lèi)的 super(int capacity) 構造方法,它的父類(lèi)是 AbstractStringBuilder ,構造方法如下:可以看到實(shí)際上StringBuilder內部使用的是 char數組 來(lái)存儲數據(String、StringBuffer也是),這里 capacity 的值指定了數組的大小。結合 StringBuilder 的無(wú)參構造函數,可以知道默認的大小是 16 個(gè)字符。
也就是說(shuō)如果待拼接的字符串總長(cháng)度不小于16的字符的話(huà),那么其實(shí)直接拼接和我們手動(dòng)寫(xiě)StringBuilder區別不大,但是我們自己構造StringBuilder類(lèi)可以指定數組的大小,避免分配過(guò)多的內存。
現在我們再看看 StringBuilder.append 方法內部做了什么事:
append方法 :
ensureCapacityInternal 方法,當拼接后的字符串總大小大于內部數組 value 的大小時(shí),就必須先擴容才能拼接,擴容的代碼如下:
StringBuilder 在擴容時(shí)把容量增大到 當前容量的兩倍+2 ,這是很可怕的,如果在構造的時(shí)候沒(méi)有指定容量,那么很有可能在擴容之后占用了浪費大量的內存空間。其次擴容后還調用了 Arrays.copyOf 方法,這個(gè)方法把擴容前的數據復制到擴容后的空間內,這樣做的原因是: StringBuilder 內部使用 char數組 存放數據,java的數組是不可擴容的,所以只能重新申請一片內存空間,并把已有的數據復制到新的空間去,這里它最終調用了 System.arraycopy 方法來(lái)復制,這是一個(gè)native方法,底層直接操作內存,所以比我們用循環(huán)來(lái)復制要塊的多,即便如此,大量申請內存空間和復制數據帶來(lái)的影響也不可忽視。使用 + 拼接和使用 StringBuilder 比較


創(chuàng )建了太多的StringBuilder對象 ,而且在每次循環(huán)過(guò)后str越來(lái)越大,導致每次申請的內存空間越來(lái)越大,并且當str長(cháng)度大于16時(shí),每次都要擴容兩次!而實(shí)際上 toString 方法在創(chuàng )建 String 對象時(shí),調用了 Arrays.copyOfRange方法來(lái)復制數據,此時(shí)相當于每執行一次,擴容了兩次,復制了3次數據,這樣的代價(jià)是相當高的。
這段代碼的執行時(shí)間在我的機器上都是0ms(小于1ms)和1ms,而上面那段代碼則大約在380ms!效率的差距相當明顯。
同樣是上面的代碼,將循環(huán)次數調整為 1000000 時(shí),在我的機器上,有指定 capacity 時(shí)耗時(shí)大約20ms,沒(méi)有指定 capacity 時(shí)耗時(shí)大約29ms,這個(gè)差距雖然和直接使用 + 操作符有了很大的提升(且循環(huán)次數增大了100倍),但是它依舊會(huì )觸發(fā)多次擴容和復制。
將上面的代碼改成使用 StringBuffer ,在我的機器上,耗時(shí)大約為33ms,這是因為 StringBuffer 在大部分方法上都加上了 synchronized 關(guān)鍵字來(lái)保證線(xiàn)程安全,執行效率有一定程度上的降低。
使用 String.concat 拼接
現在再看這段代碼:

String.concat 方法,在我的機器上,執行時(shí)間大約為130ms,雖然直接相加要好的多,但是比起使用 StringBuilder 還要太多了,似乎沒(méi)什么用。其實(shí)并不是,在很多時(shí)候,我們只需要連接兩個(gè)字符串,而不是多個(gè)字符串的拼接,這個(gè)時(shí)候使用 String.concat 方法比 StringBuilder 要簡(jiǎn)潔且效率要高。
上面這段是 String.concat 的源碼,在這個(gè)方法中,調用了一次Arrays.copyOf,并且指定了 len + otherLen ,相當于分配了一次內存空間,并分別從str1和str2各復制一次數據。而如果使用 StringBuilder 并指定 capacity ,相當于分配一次內存空間,并分別從str1和str2各復制一次數據,最后因為調用了 toString 方法,又復制了一次數據。
結論
現在根據上面的分析和測試可以知道:
Java中字符串拼接不要直接使用 + 拼接。
使用StringBuilder或者StringBuffer時(shí),盡可能準確地估算capacity,并在構造時(shí)指定,避免內存浪費和頻繁的擴容及復制。
在沒(méi)有線(xiàn)程安全問(wèn)題時(shí)使用 StringBuilder , 否則使用 StringBuffer 。
兩個(gè)字符串拼接直接調用 String.concat 性能最好。
關(guān)于 String 的其他最佳實(shí)踐:
用 equals 時(shí)總是把能確定不為空的變量寫(xiě)在左邊,如使用 ''.equals(str) 判斷空串,避免空指針異常。
第二點(diǎn)是用來(lái)排擠第一點(diǎn)的.. 使用 str != null && str.length() != 0 來(lái)判斷空串,效率比第一點(diǎn)高。
在需要把其他對象轉換為字符串對象時(shí),使用 String.valueOf(obj) 而不是直接調用 obj.toString() 方法,因為前者已經(jīng)對空值進(jìn)行檢測了,不會(huì )拋出空指針異常。
使用 String.format() 方法對字符串進(jìn)行格式化輸出。
在JDK 7及以上版本,可以在 switch 結構中使用字符串了,所以對于較多的比較,使用 switch 代替 if-else 。
我暫時(shí)想的起來(lái)的就這么幾個(gè)了.. 請大家幫忙補充補充...
聯(lián)系客服