歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> String VS StringBuilder VS StringBuffer In Java

String VS StringBuilder VS StringBuffer In Java

日期:2017/3/1 9:11:56   编辑:Linux編程

簡單說說 String

String 是不可變的,一旦定義了,就不能再去修改字符串的內容。

先看下面兩行代碼:

String a = "Hello";
a = a + " world"

通常情況下很容易誤解為修改了字符串對象的內容。其實不然,真實的操作則是

  1. "Hello" 是一個字符串對象,被賦給了引用 a;
  2. " world" 也是一個字符串對象,和 "Hello" 拼接生成一個新的字符串對象又被賦給了 a;

並不是 "Hello" 改變了,而是指向 "Hello" 的引用 a重新指向了新對象。

StringBuilder

StringBuilder 在很大程度上類似 ArrayList:

StringBulder ArrayList

維護了一個 char 數組

(其實這個數組屬於它的父類 AbstractStringBuilder)

維護了一個 Object 數組 append 方法向後面增加新元素 add(E e) 方法向後面增加新元素 insert 方法向中間某位置插入新元素 add(int index, E e) 向某位置增加新元素 deleteCharAt(int index) 刪除某位置的元素 remove(int index) 刪除某位置的元素 添加元素時候空間不夠會動態擴容 就羅列這麼多吧~

很明顯如果需要連續拼接很多字符串的話 StringBuilder 比 String 更加方便。而且在性能方面也有考究這點我們稍後再說。

StringBuffer

StringBuffer 基本上和 StringBuilder 完全一樣了。明顯的不同就是 StringBuffer 是線程安全的,除了構造方法之外的所有方法都用了 synchronized 修飾。

相對來說安全一些,但是性能上要比 StringBuilder 差一些了。

字符串拼接探索

先看一段代碼:

public class Test {
    public static void main(String[] args) {
        String aa = "33";
        aa = aa + 3 + 'x' + true + "2";
        aa = aa + 8;
        String bb = aa + "tt";
        System.out.println(bb);
    }
}

使用編譯工具編譯

javac Test.java

同級目錄會生成 Test.class,我們再對 Test.class 進行反匯編

javap Test.class

得到下面的代碼:

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String 33
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: aload_1
      11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      14: iconst_3
      15: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      18: bipush        120
      20: invokevirtual #7                  // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
      23: iconst_1
      24: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
      27: ldc           #9                  // String 2
      29: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      32: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      35: astore_1
      36: new           #3                  // class java/lang/StringBuilder
      39: dup
      40: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      43: aload_1
      44: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      47: bipush        8
      49: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      52: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      55: astore_1
      56: new           #3                  // class java/lang/StringBuilder
      59: dup
      60: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      63: aload_1
      64: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      67: ldc           #11                 // String tt
      69: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      72: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      75: astore_2
      76: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
      79: aload_2
      80: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      83: return
} 
  1. 在第一次拼接的時候(連續加號),先創建了一個 StringBuilder 對象,然後 append 當前的字符串對象 aa,接著連續 append 將要拼接的元素,最後 toString() 返回拼接後的字符串對象賦給 aa;
  2. 第二次拼接,同樣是創建一個 StringBuilder 對象,然後 append 當前的字符串對象 aa,接著 append 8,最後 toString() 返回拼接後的字符串對象賦給 aa;
  3. 第三次拼接,同樣是創建一個 StringBuilder 對象,然後 append 當前的字符串對象 aa,接著 append "tt",最後 toString() 返回拼接後的字符串對象賦給 bb;

我們把每出現一次 “=” 算成一次拼接,那麼每次拼接都會創建一個 StringBuilder 對象。

當遇到大規模的場景中,比如循環次數很多,就像下面的例子:

public class Test1 {
    public static void main(String[] args) {
        Test1 test = new Test1();
        System.out.println(test.testString());
        System.out.println(test.testStringBuilder());
    }
    
    public long testString(){
        String a = "";
        long start = Calendar.getInstance().getTimeInMillis();
        for(int i=0;i<100000;i++){
            a += i;
        }
        long end = Calendar.getInstance().getTimeInMillis();
        return end-start;
    }
    
    public long testStringBuilder(){
        StringBuilder a = new StringBuilder();
        long start = Calendar.getInstance().getTimeInMillis();
        for(int i=0;i<100000;i++){
            a.append(i);
        }
        long end = Calendar.getInstance().getTimeInMillis();
        return end-start;
    }
}

輸出:

22243
16

耗時比較,前者呈指數級增長,而後者是線性增長。性能上相差甚遠。

甚至如果我們已經知道了容量,還可以繼續優化,一次性分配一個 StringBuilder,避免擴容時候的開銷。參考下面例子。

public class Test2 {
    public static void main(String[] args) {
        Test2 test = new Test2();
        for(int i=0;i<5;i++){
            System.out.println(test.testStringBuilder() + "---" + test.testStringBuilder2());
        }
    }
    
    public long testStringBuilder(){
        StringBuilder a = new StringBuilder();
        long start = Calendar.getInstance().getTimeInMillis();
        for(int i=0;i<10000000;i++){
            a.append(1);
        }
        long end = Calendar.getInstance().getTimeInMillis();
        return end-start;
    }
    
    public long testStringBuilder2(){
        StringBuilder a = new StringBuilder(10000000);
        long start = Calendar.getInstance().getTimeInMillis();
        for(int i=0;i<10000000;i++){
            a.append(1);
        }
        long end = Calendar.getInstance().getTimeInMillis();
        return end-start;
    }
}

輸出:

78---16
62---31
47---15
63---31
47---31

提前分配,耗時更短~

原則很簡單:不要使用字符串連接操作符來合並多個字符串,除非性能無關緊要。應該使用 StringBuilder 的 append 方法。

Copyright © Linux教程網 All Rights Reserved