從為什麼 String=String 談到 StringBuilder和StringBuffer
前言
有這麼一段代碼:
運行結果是什麼?答案當然是true。對,答案的確是true,但是這是為什麼呢?很多人第一反應肯定是兩個」123″的String當然相等啊,這還要想。但是」==」在Java比較的不是兩個對象的值,而是比較兩個對象的引用是否相等,和兩個String都是」123″又有什麼關係呢?或者我們把程序修改一下
這時候運行結果就是false了,因為儘管兩個String對象都是」234″,但是str2和str3是兩個不同的引用,所以返回的false。OK,圍繞第一段代碼返回true,第二段代碼返回false,開始文章的內容。
為什麼String=String?
在JVM中有一塊區域叫做常量池,關於常量池,我在寫虛擬機的時候有專門提到http://www.cnblogs.com/xrq730/p/4827590.html。常量池中的數據是那些在編譯期間被確定,並被保存在已編譯的.class文件中的一些數據。除了包含所有的8種基本數據類型(char、byte、short、int、long、float、double、boolean)外,還有String及其數組的常量值,另外還有一些以文本形式出現的符號引用。
Java棧的特點是存取速度快(比堆塊),但是空間小,數據生命周期固定,只能生存到方法結束。我們定義的boolean b = true、char c = 『c』、String str = 「123」,這些語句,我們拆分為幾部分來看:
1、true、c、123,這些等號右邊的指的是編譯期間可以被確定的內容,都被維護在常量池中
2、b、c、str這些等號左邊第一個出現的指的是一個引用,引用的內容是等號右邊數據在常量池中的地址
3、boolean、char、String這些是引用的類型
棧有一個特點,就是數據共享。回到我們第一個例子,第五行String str0 = 「123″,編譯的時候,在常量池中創建了一個常量」123″,然後走第六行String str1 = 「123″,先去常量池中找有沒有這個」123″,發現有,str1也指向常量池中的」123″,所以第七行的str0 == str1返回的是true,因為str0和str1指向的都是常量池中的」123″這個字元串的地址。當然如果String str1 = 「234″,就又不一樣了,因為常量池中沒有」234″,所以會在常量池中創建一個」234″,然後str1代表的是這個」234″的地址。分析了String,其實其他基本數據類型也都是一樣的:先看常量池中有沒有要創建的數據,有就返回數據的地址,沒有就創建一個。
第二個例子呢?Java虛擬機的解釋器每遇到一個new關鍵字,都會在堆內存中開闢一塊內存來存放一個String對象,所以str2、str3指向的堆內存中雖然存儲的是相等的」234″,但是由於是兩塊不同的堆內存,因此str2 == str3返回的仍然是false,網上找到一張圖表示一下這個概念:
為什麼要使用StringBuilder和StringBuffer拼接字元串?
大家在開發中一定有一個原則是」利用StringBuilder和StringBuffer拼接字元串」,但是為什麼呢?用一段代碼來分析一下:
這段代碼,我們找到編譯後的StringTest.class文件,使用」javap -verbose StringTest」或者」javap -c StringTest」都可以,反編譯一下class獲取到對應的位元組碼:
public void testStringPlus(); Code: 0: ldc #17 // String 111 2: astore_1 3: new #19 // class java/lang/StringBuilder 6: dup 7: aload_1 8: invokestatic #21 // Method java/lang/String.valueOf:(Ljava/lang/Object;)L java/lang/String; 11: invokespecial #27 // Method java/lang/StringBuilder."":(Ljava/lang/S tring;)V 14: ldc #30 // String 222 16: invokevirtual #32 // Method java/lang/StringBuilder.append:(Ljava/lang/Str ing;)Ljava/lang/StringBuilder; 19: invokevirtual #36 // Method java/lang/StringBuilder.toString:()Ljava/lang/ String; 22: astore_1 23: new #19 // class java/lang/StringBuilder 26: dup 27: aload_1 28: invokestatic #21 // Method java/lang/String.valueOf:(Ljava/lang/Object;)L java/lang/String; 31: invokespecial #27 // Method java/lang/StringBuilder."":(Ljava/lang/S tring;)V 34: ldc #40 // String 333 36: invokevirtual #32 // Method java/lang/StringBuilder.append:(Ljava/lang/Str ing;)Ljava/lang/StringBuilder; 39: invokevirtual #36 // Method java/lang/StringBuilder.toString:()Ljava/lang/ String; 42: astore_1 43: getstatic #42 // Field java/lang/System.out:Ljava/io/PrintStream; 46: aload_1 47: invokevirtual #48 // Method java/io/PrintStream.println:(Ljava/lang/String ;)V 50: return }
這段位元組碼不用看得很懂,大致上能明白就好,意思很明顯:編譯器每次碰到」+」的時候,會new一個StringBuilder出來,接著調用append方法,在調用toString方法,生成新字元串。
那麼,這意味著,如果代碼中有很多的」+」,就會每個」+」生成一次StringBuilder,這種方式對內存是一種浪費,效率很不好。
在Java中還有一種拼接字元串的方式,就是String的concat方法,其實這種方式拼接字元串也不是很好,具體原因看一下concat方法的實現:
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
意思就是通過兩次字元串的拷貝,產生一個新的字元數組buf[],再根據字元數組buf[],new一個新的String對象出來,這意味著concat方法調用N次,將發生N*2次數組拷貝以及new出N個String對象,無論對於時間還是空間都是一種浪費。
根據上面的解讀,由於」+」拼接字元串與String的concat方法拼接字元串的低效,我們才需要使用StringBuilder和StringBuffer來拼接字元串。以StringBuilder為例:
public class TestMain { public static void main(String[] args) { StringBuilder sb = new StringBuilder("111"); sb.append("222"); sb.append("111"); sb.append("111"); sb.append("444"); System.out.println(sb.toString()); } }
StringBuffer和StringBuilder原理一樣,無非是在底層維護了一個char數組,每次append的時候就往char數組裡面放字元而已,在最終sb.toString()的時候,用一個new String()方法把char數組裡面的內容都轉成String,這樣,整個過程中只產生了一個StringBuilder對象與一個String對象,非常節省空間。StringBuilder唯一的性能損耗點在於char數組不夠的時候需要進行擴容,擴容需要進行數組拷貝,一定程度上降低了效率。
StringBuffer和StringBuilder用法一模一樣,唯一的區別只是StringBuffer是線程安全的,它對所有方法都做了同步,StringBuilder是線程非安全的,所以在不涉及線程安全的場景,比如方法內部,盡量使用StringBuilder,避免同步帶來的消耗。
真的不能用」+」拼接字元串?
雖然說不要用」+」拼接字元串,因為會產生大量的無用StringBuilder對象,但也不是不可以,比如可以使用以下的方式:
就這種連續+的情況,實際上編譯的時候JVM會只產生一個StringBuilder並連續append等號後面的字元串。
但是這麼寫得很少,主要原因有兩點:
1、例子比較簡單,但實際上大量的「+」會導致代碼的可讀性非常差
2、待拼接的內容可能從各種地方獲取,比如調用介面、從.properties文件中、從.xml文件中,這樣的場景下儘管用多個「+」的方式也不是不可以,但會讓代碼維護性不太好
※飛機製造巨頭空客想搞飛行汽車,草圖都已經畫出來了
※看上腦電穿戴設備市場,宏智力已擁有7萬APP註冊用戶,形成社群化優勢
※獨角獸的成功之路
※不懂服務設計?看看它的演變歷史你就知道了!
TAG:推酷 |
※Integer和int及String的總結
※SQL Server中LIKE %search_string% 索引查找(Index Seek)淺析
※從JDK源碼看StringBuffer
※toString 小技巧
※String.replaceAll方法,正則妙用
※深入談談String.intern在JVM的實現
※JSON程序的stringify()
※stringr包詳解
※String str = "ab" +"cd";共創建幾個對象
※蘋果收購語音應用公司Pullstring 助Siri升級
※誰在關心toString的性能?
※蘋果收購語音應用初創公司Pullstring,為提高Siri的市場競爭力
※C井——分享幾種常用的編碼轉換,base64、MD5、string
※INXX 2019 「SuperstrINg/超弦」 大秀
※String Chain:通證經濟下的社交改革紅利能否走通?
※10 個有關 String 的面試問題
※技術分享:String不可變性
※蘋果擬4000萬美元收購語音助手開發商Pullstring
※String(字元串):一切方法都在掌握之中
※30天快速掌握js01之簡單數據類型String知識點詳解