java中String、StringBuffer、StringBuilder是編程中經(jīng)常使用的字符串類,在上一篇博文中我們已經(jīng)熟悉String字符串的特性和使用,而StringBuffer、StringBuilder又是怎么樣的字符串類呢??他們之間的區(qū)別和關(guān)系又是什么呢??這問題經(jīng)常在面試中會問到,現(xiàn)在總結(jié)一下,看看他們的不同與相同。

1.可變與不可變

1)String類中使用字符數(shù)組保存字符串,如下就是,因為有“final”修飾符,所以可以知道string對象是不可變的。

    private final char value[];

String的值是不可變的,這就導(dǎo)致每次對String的操作都會生成新的String對象,不僅效率低下,而且大量浪費有限的內(nèi)存空間。

1 String a = "a"; //假設(shè)a指向地址0x00012 a = "b";//重新賦值后a指向地址0x0002,但0x0001地址中保存的"a"依舊存在,但已經(jīng)不再是a所指向的,a 已經(jīng)指向了其它地址。

因此String的操作都是改變賦值地址而不是改變值操作。

2)StringBuilder與StringBuffer都繼承自AbstractStringBuilder類,在AbstractStringBuilder中也是使用字符數(shù)組保存字符串,如下就是,可知這兩種對象都是可變的。

    char[] value;

StringBuffer是可變類,和線程安全的字符串操作類,任何對它指向的字符串的操作都不會產(chǎn)生新的對象。 每個StringBuffer對象都有一定的緩沖區(qū)容量,當(dāng)字符串大小沒有超過容量時,不會分配新的容量,當(dāng)字符串大小超過容量時,會自動增加容量。

1 StringBuffer buf=new StringBuffer(); //分配長16字節(jié)的字符緩沖區(qū)2 StringBuffer buf=new StringBuffer(512); //分配長512字節(jié)的字符緩沖區(qū)3 StringBuffer buf=new StringBuffer("this is a test")//在緩沖區(qū)中存放了字符串,并在后面預(yù)留了16字節(jié)的空緩沖區(qū)。

StringBuffer和StringBuilder類功能基本相似,主要區(qū)別在于StringBuffer類的方法是多線程、安全的,而StringBuilder不是線程安全的,相比而言,StringBuilder類會略微快一點。對于經(jīng)常要改變值的字符串應(yīng)該使用StringBuffer和StringBuilder類。

2.是否多線程安全

String中的對象是不可變的,也就可以理解為常量,顯然線程安全

AbstractStringBuilder是StringBuilder與StringBuffer的公共父類,定義了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。

StringBuffer對方法加了同步鎖或者對調(diào)用的方法加了同步鎖,所以是線程安全的。看如下源碼:

 

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

1 public synchronized StringBuffer reverse() {2     super.reverse();3     return this;4 }5 6 public int indexOf(String str) {7     return indexOf(str, 0);        //存在 public synchronized int indexOf(String str, int fromIndex) 方法8 }

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

 

StringBuilder并沒有對方法進行加同步鎖,所以是非線程安全的

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

StringBuffer是線程安全的,這意味著它們已經(jīng)同步方法來控制訪問,以便只有一個線程可以在同一時間訪問一個StringBuffer對象同步代碼。因此,StringBuffer的對象通常在多線程環(huán)境中是安全的,使用多個線程可以試圖同時訪問相同StringBuffer對象。

StringBuilder類非常相似的StringBuffer,不同之處在于它的訪問不同步的,因此,它不是線程安全的。由于不同步,StringBuilder的性能可以比StringBuffer更好。因此,如果在單線程環(huán)境中工作,使用StringBuilder,而不是StringBuffer可能會有更高的性能。這也類似其他情況,如StringBuilder的局部變量(即一個方法中的一個變量),其中只有一個線程會訪問一個StringBuilder對象。

 

3.StringBuffer和StringBuilder類的速度比較

一般情況下,速度從快到慢:StringBuilder>StringBuffer>String,這種比較是相對的,不是絕對的。(要考慮程序是單線程還是多線程)

接下來,我直接貼上測試過程和結(jié)果的代碼,一目了然:

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

 1 package com.hysum.test; 2  3 public class StringTest { 4     final static int time = 50000; //循環(huán)次數(shù)  5     /* 6      * String類測試方法 7      */ 8     public void test(String s){ 9         long begin = System.currentTimeMillis();//獲取當(dāng)前系統(tǒng)時間(毫秒數(shù)),開始10         for(int i=0; i<time; i++){11         s += "add";12         }13         long over = System.currentTimeMillis();//獲取當(dāng)前系統(tǒng)時間(毫秒數(shù)),結(jié)束14         System.out.println("操作"+s.getClass().getName()+"類型使用的時間為:"+(over-begin)+"毫秒");15         }16     /*17      * StringBuffer類測試方法18      */19     public void test(StringBuffer s){20         long begin = System.currentTimeMillis();21         for(int i=0; i<time; i++){22         s.append("add");23         }24         long over = System.currentTimeMillis();25         System.out.println("操作"+s.getClass().getCanonicalName()+"類型使用的時間為:"+(over-begin)+"毫秒");26         }27     /*28      * StringBuilder類測試方法29      */30     public void test(StringBuilder s){31         long begin = System.currentTimeMillis();32         for(int i=0; i<time; i++){33         s.append("add");34         }35         long over = System.currentTimeMillis();36         System.out.println("操作"+s.getClass().getName()+"類型使用的時間為:"+(over-begin)+"毫秒");37         }38 39     /*對 String 直接進行字符串拼接的測試*/40     public void test2(){//操作字符串對象引用相加類型使用的時間41         String s2 = "abcd";42         long begin = System.currentTimeMillis();43         for(int i=0; i<time; i++){44         String s = s2 + s2 +s2;45         }46         long over = System.currentTimeMillis();47         System.out.println("操作字符串對象引用相加類型使用的時間為:"+(over-begin)+"毫秒");48         }49     public void test3(){//操作字符串相加使用的時間50         long begin = System.currentTimeMillis();51         for(int i=0; i<time; i++){52         String s = "abcd" + "abcd" +  "abcd";53         }54         long over = System.currentTimeMillis();55         System.out.println("操作字符串相加使用的時間為:"+(over-begin)+"毫秒");56         } 
57     public static void main(String[] args) {58         // TODO Auto-generated method stub59         String s1 =  "abcd";60         StringBuffer st1 = new StringBuffer( "abcd");61         StringBuilder st2 = new StringBuilder( "abcd");62         StringTest tc = new StringTest();63         tc.test(s1);64         tc.test(st1);65         tc.test(st2);66         tc.test2();67         tc.test3(); 
68     }69 70 }

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

運行結(jié)果:

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

結(jié)果分析:

從上面的結(jié)果可以看出,不考慮多線程,采用String對象時,執(zhí)行時間比其他兩個都要高得多,而采用StringBuffer對象和采用StringBuilder對象的差別也比較明顯;而以String類為例,操作字符串對象引用相加類型使用的時間比直接/操作字符串相加使用的時間也多得多。由此可見,如果我們的程序是在單線程下運行,或者是不必考慮到線程同步問題,我們應(yīng)該優(yōu)先使用StringBuilder類;如果要保證線程安全,自然是StringBuffer;能直接操作字符串不用字符串引用就直接操作字符串。

4、StringBuilder與StringBuffer共同點

StringBuilder與StringBuffer有公共父類AbstractStringBuilder(抽象類)。

StringBuilder、StringBuffer的方法都會調(diào)用AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer會在方法上加synchronized關(guān)鍵字,進行同步。

那么我們接來下看一下它們的主要方法吧~

方法說明
StringBuffer append(參數(shù))追加內(nèi)容到當(dāng)前StringBuffer對象的末尾,類似于字符串的連接
StringBuffer deleteCharAt(int index)刪除指定位置的字符,然后將剩余的內(nèi)容形成新的字符串
StringBuffer insert(位置, 參數(shù))在StringBuffer對象中插入內(nèi)容,然后形成新的字符串
StringBuffer reverse()將StringBuffer對象中的內(nèi)容反轉(zhuǎn),然后形成新的字符串
void setCharAt(int index, char ch)修改對象中索引值為index位置的字符為新的字符ch
void trimToSize()將StringBuffer對象的中存儲空間縮小到和字符串長度一樣的長度,減少空間的浪費,和String的trim()是一樣的作用
StringBuffer delete(int start, int end)刪除指定區(qū)域的字符串
StringBuffer replace(int start, int end, String s) 用新的字符串替換指定區(qū)域的字符串
void setlength(int n)設(shè)置字符串緩沖區(qū)大小
int capacity()獲取字符串的容量
void ensureCapacity(int n)確保容量至少等于指定的最小值。如果當(dāng)前容量小于該參數(shù),然后分配一個新的內(nèi)部數(shù)組容量更大。新的容量是較大的.
getChars(int start,int end,char chars[],int charStart);將字符串的子字符串復(fù)制給數(shù)組

以下是各個方法的代碼示例:

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

 1 public static void main(String[] args) { 2         // TODO Auto-generated method stub 3         StringBuilder str=new StringBuilder("學(xué)習(xí) java 編程"); 4          5         //增加字符串內(nèi)容的方法 6         //append(參數(shù)),追加內(nèi)容到當(dāng)前對象的末尾 7         str.append("學(xué)習(xí)使我快樂"); 8         System.out.println("追加內(nèi)容到當(dāng)前對象的末尾:"+str); 9         // insert(位置, 參數(shù)),在對象中插入內(nèi)容10         str.insert(10,',');11         System.out.println("在對象中插入內(nèi)容:"+str);12         13         //操作字符串內(nèi)容的方法14         //delete(int start, int end),刪除指定區(qū)域的字符串15         str.delete(11, 17);16         System.out.println("刪除指定區(qū)域的字符串:"+str);17         //deleteCharAt(int index),刪除指定位置的字符18         str.deleteCharAt(10);19         System.out.println("刪除指定位置的字符:"+str);20         //setCharAt(int index, char newChar),修改對象中索引值為index位置的字符為新的字符ch21         str.setCharAt(3, 'J');22         System.out.println("修改對象中索引值為index位置的字符為新的字符ch:"+str);23         //replace(int start, int end, String s), 用新的字符串替換指定區(qū)域的字符串24         str.replace(4, 7, "AVA");25         System.out.println("用新的字符串替換指定區(qū)域的字符串:"+str);26         // reverse()內(nèi)容反轉(zhuǎn)27         str.reverse();28         System.out.println("內(nèi)容反轉(zhuǎn):"+str);29         //將字符串的子字符串復(fù)制給數(shù)組。30         char[] ch  = new char[5];31         str.getChars(0, 4, ch, 0);32         System.out.println("將字符串的子字符串復(fù)制給數(shù)組:"+Arrays.toString(ch));33 34         35         36         37         StringBuilder str2=new StringBuilder(30);//創(chuàng)建一個長度為30的字符串38         str2.append("JAVA");39         System.out.println("字符串長度為:"+str2.length());//length(),獲取字符串長度40         System.out.println("字符串容量為:"+str2.capacity());//capacity(),獲取字符串的容量41         //有關(guān)字符串空間的方法42         //setLength(int newSize),設(shè)置字符串緩沖區(qū)大小43         str2.setLength(20);44         System.out.println("字符串長度為:"+str2.length());45         System.out.println("字符串容量為:"+str2.capacity());46         //ensureCapacity(int n),重新設(shè)置字符串容量的大小47         str2.ensureCapacity(20);48         System.out.println("字符串長度為:"+str2.length());49         System.out.println("字符串容量為:"+str2.capacity());50         str2.ensureCapacity(35);51         System.out.println("字符串長度為:"+str2.length());52         System.out.println("字符串容量為:"+str2.capacity());53         //trimToSize(),存儲空間縮小到和字符串長度一樣的長度54         str2.trimToSize();55         System.out.println("字符串長度為:"+str2.length());56         System.out.println("字符串容量為:"+str2.capacity());57         58         59     }60 61 }

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

運行結(jié)果:

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

結(jié)果分析:

1、在使用有范圍的參數(shù)方法時,要注意范圍包括開頭不包括結(jié)尾!

2、insert方法的位置是你要插入的位置,不是插入前一個位置!

3、getChars方法中注意字符數(shù)組的長度一定要大于等于begin到end之間字符的長度!

4、length是字符串內(nèi)容的長度,而capacity是字符串容量(包括緩存區(qū))的長度!

5、ensureCapacity方法是確保容量至少等于指定的最小值。如果當(dāng)前容量小于該參數(shù),然后分配一個新的內(nèi)部數(shù)組容量更大(不是你指定的值,系統(tǒng)自動分配一個空間)。如果當(dāng)前容量不小于該參數(shù),則容量不變。

6、trimToSize(),存儲空間縮小到和字符串長度一樣的長度。避免空間的浪費!


總結(jié)

(1).如果要操作少量的數(shù)據(jù)用 = String 
(2).單線程操作字符串緩沖區(qū) 下操作大量數(shù)據(jù) = StringBuilder 
(3).多線程操作字符串緩沖區(qū) 下操作大量數(shù)據(jù) = StringBuffer

 

參考文獻:

http://www.jb51.net/article/33398.htm

http://blog.csdn.net/mad1989/article/details/26389541

    本文如果對大家的學(xué)習(xí)有幫助,請點擊下方的“推薦”或者“收藏”!您的支持將是我最大的動力,謝謝???(●˙?˙●)???再來一個不要臉的求“關(guān)注”   

作者: 云開的立夏

出處: http://www.cnblogs.com/hysum/>

關(guān)于作者:本人目前還在上學(xué),小白一枚,希望能把學(xué)過的知識與大家分享,請多多賜教!

版權(quán)聲明:本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出 原文鏈接

大家寫文都不容易,請尊重勞動成果~這里謝謝大家啦(*/ω\*) 如有問題, 可郵件(hysum626@162.com)咨詢.

http://www.cnblogs.com/hysum/p/7125651.html