1. 程式人生 > >全面解釋java中StringBuilder、StringBuffer、String類之間的關系

全面解釋java中StringBuilder、StringBuffer、String類之間的關系

() 大於等於 修飾 軟件 進行 lan lin 字符 str

StringBuilder、StringBuffer、String類之間的關系



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

1.可變與不可變

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

    private final char value[];

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

1 String a = "a"; //假設a指向地址0x0001
2 a = "b";//重新賦值後a指向地址0x0002,但0x0001地址中保存的"a"依舊存在,但已經不再是a所指向的,a 已經指向了其它地址。 

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

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

    char[] value;

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

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

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

2.是否多線程安全

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

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

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

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 }

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

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

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

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

3.StringBuffer和StringBuilder類的速度比較

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

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

 1 package com.hysum.test;
 2 
 3 public class StringTest {
 4     final static int time = 50000; //循環次數 
 5     /*
 6      * String類測試方法
 7      */
 8     public void test(String s){
 9         long begin = System.currentTimeMillis();//獲取當前系統時間(毫秒數),開始
10         for(int i=0; i<time; i++){
11         s += "add";
12         }
13         long over = System.currentTimeMillis();//獲取當前系統時間(毫秒數),結束
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 stub
59         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 }

運行結果:

技術分享

結果分析:

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

4、StringBuilder與StringBuffer共同點

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

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

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

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

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

 1 public static void main(String[] args) {
 2         // TODO Auto-generated method stub
 3         StringBuilder str=new StringBuilder("學習 java 編程");
 4         
 5         //增加字符串內容的方法
 6         //append(參數),追加內容到當前對象的末尾
 7         str.append("學習使我快樂");
 8         System.out.println("追加內容到當前對象的末尾:"+str);
 9         // insert(位置, 參數),在對象中插入內容
10         str.insert(10,‘,‘);
11         System.out.println("在對象中插入內容:"+str);
12         
13         //操作字符串內容的方法
14         //delete(int start, int end),刪除指定區域的字符串
15         str.delete(11, 17);
16         System.out.println("刪除指定區域的字符串:"+str);
17         //deleteCharAt(int index),刪除指定位置的字符
18         str.deleteCharAt(10);
19         System.out.println("刪除指定位置的字符:"+str);
20         //setCharAt(int index, char newChar),修改對象中索引值為index位置的字符為新的字符ch
21         str.setCharAt(3, ‘J‘);
22         System.out.println("修改對象中索引值為index位置的字符為新的字符ch:"+str);
23         //replace(int start, int end, String s), 用新的字符串替換指定區域的字符串
24         str.replace(4, 7, "AVA");
25         System.out.println("用新的字符串替換指定區域的字符串:"+str);
26         // reverse()內容反轉
27         str.reverse();
28         System.out.println("內容反轉:"+str);
29         //將字符串的子字符串復制給數組。
30         char[] ch  = new char[5];
31         str.getChars(0, 4, ch, 0);
32         System.out.println("將字符串的子字符串復制給數組:"+Arrays.toString(ch));
33 
34         
35         
36         
37         StringBuilder str2=new StringBuilder(30);//創建一個長度為30的字符串
38         str2.append("JAVA");
39         System.out.println("字符串長度為:"+str2.length());//length(),獲取字符串長度
40         System.out.println("字符串容量為:"+str2.capacity());//capacity(),獲取字符串的容量
41         //有關字符串空間的方法
42         //setLength(int newSize),設置字符串緩沖區大小
43         str2.setLength(20);
44         System.out.println("字符串長度為:"+str2.length());
45         System.out.println("字符串容量為:"+str2.capacity());
46         //ensureCapacity(int n),重新設置字符串容量的大小
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 }

運行結果:

技術分享

結果分析:

1、在使用有範圍的參數方法時,要註意範圍包括開頭不包括結尾!

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

3、getChars方法中註意字符數組的長度一定要大於等於begin到end之間字符的長度!

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

5、ensureCapacity方法是確保容量至少等於指定的最小值。如果當前容量小於該參數,然後分配一個新的內部數組容量更大(不是你指定的值,系統自動分配一個空間)。如果當前容量不小於該參數,則容量不變。

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


總結

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

參考文獻:

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

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

全面解釋java中StringBuilder、StringBuffer、String類之間的關系