[jse] String StringBuffler StringBuilder

String 字符串常量
StringBuffer 字符串變量(線程安全)
StringBuilder 字符串變量(非線程安全)

 簡要的說, String 類型和 StringBuffer 類型的主要性能區別其實在於 String 是不可變的對象, 因此在每次對 String 類型進行改變的時候其實都等同於生成了一個新的 String 對象,然後將指針指向新的 String 對象,所以經常改變內容的字符串最好不要用 String ,因爲每次生成對象都會對系統性能產生影響,特別當內存中無引用對象多了以後, JVM 的 GC 就會開始工作,那速度是一定會相當慢的。
 而如果是使用 StringBuffer 類則結果就不一樣了,每次結果都會對 StringBuffer 對象本身進行操作,而不是生成新的對象,再改變對象引用。所以在一般情況下我們推薦使用 StringBuffer ,特別是字符串對象經常改變的情況下。而在某些特別情況下, String 對象的字符串拼接其實是被 JVM 解釋成了 StringBuffer 對象的拼接,所以這些時候 String 對象的速度並不會比 StringBuffer 對象慢,而特別是以下的字符串對象生成中, String 效率是遠要比 StringBuffer 快的:
 String S1 = “This is only a” + “ simple” + “ test”;
 StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
 你會很驚訝的發現,生成 String S1 對象的速度簡直太快了,而這個時候 StringBuffer 居然速度上根本一點都不佔優勢。其實這是 JVM 的一個把戲,在 JVM 眼裏,這個
 String S1 = “This is only a” + “ simple” + “test”; 其實就是:
 String S1 = “This is only a simple test”; 所以當然不需要太多的時間了。但大家這裏要注意的是,如果你的字符串是來自另外的 String 對象的話,速度就沒那麼快了,譬如:
String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;
String S1 = S2 +S3 + S4;
這時候 JVM 會規規矩矩的按照原來的方式去做

在大部分情況下 StringBuffer > String
StringBuffer
Java.lang.StringBuffer線程安全的可變字符序列。一個類似於 String 的字符串緩衝區,但不能修改。雖然在任意時間點上它都包含某種特定的字符序列,但通過某些方法調用可以改變該序列的長度和內容。
可將字符串緩衝區安全地用於多個線程。可以在必要時對這些方法進行同步,因此任意特定實例上的所有操作就好像是以串行順序發生的,該順序與所涉及的每個線程進行的方法調用順序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重載這些方法,以接受任意類型的數據。每個方法都能有效地將給定的數據轉換成字符串,然後將該字符串的字符追加或插入到字符串緩衝區中。append 方法始終將這些字符添加到緩衝區的末端;而 insert 方法則在指定的點添加字符。
例如,如果 z 引用一個當前內容是“start”的字符串緩衝區對象,則此方法調用 z.append("le") 會使字符串緩衝區包含“startle”,而 z.insert(4, "le") 將更改字符串緩衝區,使之包含“starlet”。
在大部分情況下 StringBuilder > StringBuffer
java.lang.StringBuilde
java.lang.StringBuilder一個可變的字符序列是5.0新增的。此類提供一個與 StringBuffer 兼容的 API,但不保證同步。該類被設計用作 StringBuffer 的一個簡易替換,用在字符串緩衝區被單個線程使用的時候(這種情況很普遍)。如果可能,建議優先採用該類,因爲在大多數實現中,它比 StringBuffer 要快。兩者的方法基本相同。
通過非官方試驗測試,StringBuilder和StringBuffer的測試總結如下:

1.  爲了獲得更好的性能,在構造 StirngBuffer  StirngBuilder 時應儘可能指定它的容量。當然,如果你操作的字符串長度不超過 16 個字符就不用了。

2.  相同情況下使用 StirngBuilder 相比使用 StringBuffer 僅能獲得 10%~15% 左右的性能提升,但卻要冒多線程不安全的風險。而在現實的模塊化編程中,負責某一模塊的程序員不一定能清晰地判斷該模塊是否會放入多線程的環境中運行,因此:除非你能確定你的系統的瓶頸是在 StringBuffer 上,並且確定你的模塊不會運行在多線程模式下,否則還是用 StringBuffer  J

3.  用好現有的類比引入新的類更重要。很多程序員在使用 StringBuffer 時是不指定其容量的(至少我見到的情況是這樣),如果這樣的習慣帶入 StringBuilder 的使用中,你將只能獲得 10 %左右的性能提升(不要忘了,你可要冒多線程的風險噢);但如果你使用指定容量的 StringBuffer ,你將馬上獲得 45% 左右的性能提升,甚至比不使用指定容量的 StirngBuilder 都快 30% 左右。

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

衆所周知string對象在java裏面是不可變(immutable)的,任何的連接,修改,其實都是在heap裏面創建了一個新的string對象,而對原來的string對象是沒有任何影響的,它們還是在那裏,不曾改變,你只是用它們的字面值去創造了新的strings而已。

string的不可變特性是非常有用的,至少這使得string是絕對的多線程安全的,那麼java是怎麼對待string對象的呢?是怎麼利用string的這個不可變特性去優化內存的使用的呢?

我們從string的兩種構造方式說起
String s1 = new String("tubie");
String s2 = "tubie";

有什麼兩樣?兩者最後都返回了一個指向字面值爲tubie的一個引用,從這個結果來看是一樣的,但其實過程和內存的變化都是大大的不同的

前者因爲使用了new關鍵字,這迫使JVM在heap上創建了一個新的string對象,然後把該對象的引用返回,任務結束;
後者因爲是"常量"賦值,所以其任務其實多了很多。JVM會首先到一個內部的string pool中找是否有存在相同值的string對象,這個string pool是java.lang.String內部維護的。如果有,就返回池裏那個對象的引用,沒有就在堆上分配個新的,然後把引用返回給用戶的同時把這個新的string,以前沒有的string加到池裏。

注意第一種方法雖然創建了一個string對象但是並沒有放到池裏,哪個對內存的利用率高清晰可辨,哪個在調用時的消耗大,做的事情多也清晰可見(哈希做的string pool搜索?)。空間和時間的trade off啊。

所以咯,如果之前string pool中沒有"tubie"這個string,那麼毫無疑問之前的s1 != s2,當然,equls的值比較是相等的,而這時如果有String s3 = "tubie";那麼 S2 == S3是成立的了,因爲池裏已經加入了s2,並且在創建s3時搜到了。

前面說到用new關鍵字創建的string對象不會自己加入到string pool裏面(在池裏的string,有種叫法叫intern strings),那就要顯示的由用戶來加入

String sa = new String("Cao Dui");
sa = sa.intern();

這時候Cao Dui就加入到字符串池裏面了。
這個intern到底做了哪些事?
首先查看了string pool,有沒有一個"Cao Dui"已經加入過了,加入的那個直接返回給sa
不存在就在堆上另外分配一個"Cao Dui"的string對象,把sa指向這個,再把sa放入到池裏。
無論是哪種情況,可以肯定的是,第一句的sa和第二句結束時的sa肯定是兩個引用了。在這裏第一個Cao Dui因爲唯一的sa已經不指向它了,所以會很快被GC給回收掉。

我們另外從剛纔的過程,intern函數的調用裏可以得到一個結論,就是如果s1.equls(s2);則必定有s1.intern() == s2.intern();這時候s1和s2其實已經在這句比較結束後指向同一個已經在string池裏面的對象了。

JVM這麼做就是因爲string的不可變特性。intern函數是一個native函數。

值得一說的是string的連接是創造了一個新的對象的。所以把sa和s1連接下,創建的就是一個完全新的string對象:
Cao Dui tubie


String的設計是一個典型的單一模式

String str1="AAAA";
String str2="AAAA";

這生成兩個對象嗎?
不是。在內存中,這是同一個對象
所以 
if(str1==str2){} 的結果應該是 true


如果要生成不同的對象,就必須
String str1=new String("AAAA");
String str2=new String("AAAA");
if(str1==str2){} 的結果應該是 false

用第一種做法雖然變量名變來變去,但內存中對象仍只有一個,這種方法可以有效地節省內存空間和提高運行效率。

由於String具有不變的性質。所以對一長串String中的每一個字符進行操作是比較浪費時間的,對String進行加減刪除替換等工作比較耗時,所以後來Sun又推出來StringBuffer方法來做這些事情。
StringBuffer類似於一個char[],
所以對數組無素做遍歷,刪除,增加,修改,查找等工作是比較快速的,完成後再把StringBuffer可以轉化成String

例: 
String a="請輸入帳號:";
String b="NNNN";
String c="請輸入密碼:";
String d="XXXX";
............

一般的做法 String result=a+b+c+d;
結果是正確的,但不是高效的代碼!


改良的做法:
StringBuffer buffer=new StringBuffer();
buffer.append(a);
buffer.append(b);
buffer.append(c);
buffer.append(d);
String result=buffer.toString();



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章