從內(nèi)存方面解釋Java中String與StringBuilder的性能差異
以前經(jīng)常在網(wǎng)上看到關(guān)于Java字符串拼接等方面的討論。看到有些Java開發(fā)人員在給新手程序員的建議中類似如下寫道:
不要使用+號(hào)拼接字符串,要使用StringBuffer或StringBuilder的append()方法來拼接字符串。
不過,用+號(hào)拼接字符串就真的那么令人討厭,難道使用+號(hào)拼接字符串就沒有一點(diǎn)可取之處嗎?
通過查閱Java API文檔中關(guān)于String類的部分內(nèi)容,我們可以看到如下片段:
“Java 語言提供對(duì)字符串串聯(lián)符號(hào)("+")以及將其他對(duì)象轉(zhuǎn)換為字符串的特殊支持。字符串串聯(lián)是通過 StringBuilder(或 StringBuffer)類及其 append 方法實(shí)現(xiàn)的。字符串轉(zhuǎn)換是通過 toString 方法實(shí)現(xiàn)的,該方法由 Object 類定義,并可被 Java中的所有類繼承?!?/p>
這段話很明確地告訴我們,在Java中使用+號(hào)拼接字符串,實(shí)際上使用的就是StringBuffer或StringBuilder及其append方法來實(shí)現(xiàn)的。
除了Java API文檔,我們還可以使用工具查看class類文件的字節(jié)碼命令來得到上述答案。 例如代碼:
public static void main(String[] args) { String a = "Hello"; String b = " world"; String str = a + b + " !"; System.out.println(str); }
通過工具查看到其對(duì)應(yīng)的字節(jié)碼命令如下:
從字節(jié)碼命令中,我們可以清楚地看到,我們編寫的如下代碼
String str = a + b + " !";
被編譯器轉(zhuǎn)換成了類似如下語句:
String str = new StringBuilder(String.valueOf(a)).append(b).append(" !").toString();
不僅如此,Java的編譯器也是一個(gè)比較聰明的編譯器,當(dāng)+號(hào)拼接的全部是字符串字面量時(shí),Java的編譯器將會(huì)在編譯時(shí)智能地將其轉(zhuǎn)換為一個(gè)完整的字符串。例如:
public static void main(String[] args) { String str = "Hello" + " world" + ", Java!"; System.out.println(str); }
Java編譯器直接將這種全是字面量的字符串拼接,在編譯時(shí)就轉(zhuǎn)換為了一個(gè)完整的字符串。
就算+號(hào)拼接的字符串中存在變量,Java編譯器也會(huì)將最前面的字符串字面量合并為一個(gè)字符串。
public static void main(String[] args) { String java = ", Java!"; String str = "Hello" + " world" + java; System.out.println(str); }
從上述可知,對(duì)于類似String str = str1 + str2 + str3 + str4,這種將多個(gè)字符串一次性拼接的操作,使用+號(hào)來進(jìn)行拼接是完全沒有問題的。
在Java中,String對(duì)象是不可變的(Immutable)。在代碼中,可以創(chuàng)建多個(gè)某一個(gè)String對(duì)象的別名。但是這些別名都是的引用是相同的。
比如s1和s2都是”droidyue.com”對(duì)象的別名,別名保存著到真實(shí)對(duì)象的引用。所以s1 = s2
String s1 = "droidyue.com"; String s2 = s1; System.out.println("s1 and s2 has the same reference =" + (s1 == s2));
并且在Java中,唯一被重載的運(yùn)算符就是字符串的拼接相關(guān)的。+,+=。除此之外,Java設(shè)計(jì)者不允許重載其他的運(yùn)算符。
在Java中,唯一被重載的運(yùn)算符就是字符串的拼接相關(guān)的。+,+=。除此之外,Java設(shè)計(jì)者不允許重載其他的運(yùn)算符。
眾所周知,在Java 1.4版本之前,字符串拼接可以使用StringBuffer,從Java 1.5開始,我們可以使用StringBuilder來拼接字符串。StringBuffer和StringBuilder的主要區(qū)別在于:StringBuffer是線程安全的,適用于多線程操作字符串;StringBuilder是線程不安全的,適合單線程下操作字符串。不過,我們的大多數(shù)字符串拼接操作都是在單線程下進(jìn)行的,因此使用StringBuilder有利于提高性能。
在Java 1.4之前,編譯器使用StringBuffer來處理+號(hào)拼接的字符串;從Java 1.5開始,編譯器大多數(shù)情況下都使用StringBuilder來處理+號(hào)拼接的字符串。
當(dāng)我們?cè)贘DK 1.4的環(huán)境下編寫代碼時(shí),對(duì)于上述這種一次性拼接多個(gè)字符串的情況,建議最好使用+號(hào)來處理。這樣,當(dāng)JDK 升級(jí)到1.5及以上版本時(shí),編譯器將會(huì)自動(dòng)將其轉(zhuǎn)換為StringBuilder來拼接字符串,從而提高字符串拼接效率。
當(dāng)然,推薦使用+號(hào)拼接字符串也僅限于在一條語句中拼接多個(gè)字符串時(shí)使用。如果分散在多條語句中拼接一個(gè)字符串,仍然建議使用StringBuffer或StringBuilder。 例如:
public static void main(String[] args) { String java = ", Java!"; String str = ""; str += "Hello"; str += " world"; str += java; System.out.println(str); }
編譯器編譯后的字節(jié)命令如下:
從上面的圖片中我們可以知道,每一條+號(hào)拼接語句,都創(chuàng)建了一個(gè)新的StringBuilder對(duì)象。這種情況在循環(huán)條件下表現(xiàn)得尤其明顯,造成了相對(duì)較大的性能損耗。因此,在多條語句中拼接字符串,強(qiáng)烈建議使用StringBuffer或StringBuilder來處理。
關(guān)于使用StringBuilder帶來的優(yōu)化
此外,在使用StringBuffer或StringBuilder的時(shí)候,我們還可以使用如下方式,進(jìn)一步提高性能(下面代碼以StringBuilder為例,StringBuffer與此類似)。
1.預(yù)測(cè)最終獲得的字符串的最大長(zhǎng)度。
StringBuilder內(nèi)部char數(shù)組的默認(rèn)長(zhǎng)度為16,當(dāng)我們append追加字符串后超過此長(zhǎng)度時(shí),StringBuilder會(huì)擴(kuò)大內(nèi)部的數(shù)組容量以滿足需要。在這個(gè)過程中,StringBuilder會(huì)創(chuàng)建一個(gè)新的較大容量的char數(shù)組,并將原數(shù)組中的數(shù)據(jù)復(fù)制到新數(shù)組中。如果我們能夠大致預(yù)測(cè)到最終拼接得到的字符串的最大長(zhǎng)度,就可以在創(chuàng)建StringBuilder對(duì)象時(shí)指定合適大小的初始容量。例如,我們需要拼接獲得含有100個(gè)字母a的字符串。即可編寫如下代碼:
StringBuilder sb = new StringBuilder(100); for (int i = 0; i < 100; i++) { sb.append('a'); } System.out.println(sb);
請(qǐng)根據(jù)實(shí)際情況進(jìn)行平衡,以創(chuàng)建適合初始容量的StringBuilder。
2.對(duì)于單個(gè)字符,盡可能地使用char類型,而不是String類型。
有些時(shí)候,我們需要在字符串后追加單個(gè)字符(例如:a),此時(shí)應(yīng)盡可能地使用
sb.append('a');
而不是使用:
sb.append("a");
- 詳細(xì)分析Java中String、StringBuffer、StringBuilder類的性能
- 深入剖析java中String、StringBuffer、StringBuilder的區(qū)別
- 淺析java中stringBuilder的用法
- Java StringBuilder和StringBuffer源碼分析
- Java中String、StringBuffer、StringBuilder的區(qū)別介紹
- Java中StringBuffer和StringBuilder區(qū)別
- Java中的StringBuilder性能測(cè)試
- java中String與StringBuilder的區(qū)別
- 全面解釋java中StringBuilder、StringBuffer、String類之間的關(guān)系
- Java之String、StringBuffer、StringBuilder的區(qū)別分析
- Java中StringBuilder字符串類型的操作方法及API整理
相關(guān)文章
IntelliJ?IDEA無公網(wǎng)遠(yuǎn)程Linux服務(wù)器環(huán)境開發(fā)過程(推薦收藏)
下面介紹如何在IDEA中設(shè)置遠(yuǎn)程連接服務(wù)器開發(fā)環(huán)境并結(jié)合Cpolar內(nèi)網(wǎng)穿透工具實(shí)現(xiàn)無公網(wǎng)遠(yuǎn)程連接,然后實(shí)現(xiàn)遠(yuǎn)程Linux環(huán)境進(jìn)行開發(fā),感興趣的朋友跟隨小編一起看看吧2023-12-12關(guān)于Java中的繼承和組合的一個(gè)錯(cuò)誤使用的例子
這篇文章主要介紹了關(guān)于Java中的繼承和組合的一個(gè)錯(cuò)誤使用的例子,需要的朋友可以參考下2016-08-08MyBatis查詢時(shí)屬性名和字段名不一致問題的解決方法
這篇文章主要給大家介紹了關(guān)于MyBatis查詢時(shí)屬性名和字段名不一致問題的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01SpringBoot整合Elasticsearch7.2.0的實(shí)現(xiàn)方法
這篇文章主要介紹了SpringBoot整合Elasticsearch7.2.0的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08基于java線程池讀取單個(gè)SQL數(shù)據(jù)庫表
這篇文章主要為大家詳細(xì)介紹了基于java線程池讀取單個(gè)SQL數(shù)據(jù)庫表,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08