String和StringBuilder和StringBuffer三兄弟

Aurora Polaris發表於2016-12-01

String和StringBuilder和StringBuffer三兄弟


前言

好久之前在寫檔案上傳的時候使用了這樣的一段程式碼

/**
* @param rootUrlStr:儲存的路徑的資料夾路徑 假設就是 D:\save
* @param fileUriStr:需要儲存的檔案的具體路徑
* @about 這是一段精簡的程式碼
* @return 儲存檔案地址
*/
public String uploadUri1(String rootUrlStr,String fileUriStr) {
    //下面我將故意使用很複雜的拼接
    //獲得檔名,故意不使用UUID.randomUUID()
    String fileNameStr=fileUriStr.substring(fileUriStr.lastIndexOf("\\"));
    String fileRealUriStr=rootUrlStr+"\\";
    fileRealUriStr+="uploadFile\\";
    fileRealUriStr+=fileNameStr;//把根路徑和檔名拼接在一起
    return fileRealUriStr;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

這種寫法很明顯的一點就是會生成很多的中間物件。
於是我後來改成了這樣

public String uploadUri2(String rootUrlStr,String fileUriStr) {
    //下面我將故意使用很複雜的拼接
    //獲得檔名,故意不使用UUID.randomUUID()
    StringBuffer fileRealUriStr=new StringBuffer(rootUrlStr);
    String fileNameStr=fileUriStr.substring(fileUriStr.lastIndexOf("\\"));
    fileRealUriStr.append("uploadFile\\").append(fileNameStr);
    return fileRealUriStr.toString();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用StringBuffer去避免生成太多臨時字串。
當然有更好的方法,那就是使用Paths

public String uploadUri3(String rootUrlStr,String fileUriStr) {
    //下面使用Path
    //獲得檔名,故意不使用UUID.randomUUID()
    String fileNameStr=fileUriStr.substring(fileUriStr.lastIndexOf("\\"));
    Path path=Paths.get(rootUrlStr,"uploadFile",fileNameStr);
    return path.toString();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Paths會自動補全”\”,使得程式碼比較直觀好看。他不是今天的主角,所以我就不展開講了。

//測試程式碼
public static void main(String[] args) {
    String string1=new MyText().uploadUri1("D:save","I:\\JAVA\\java.txt");
    String string2=new MyText().uploadUri2("D:save","I:\\JAVA\\java.txt");
    String string3=new MyText().uploadUri3("D:save","I:\\JAVA\\java.txt");
    System.out.println(string1);
    System.out.println(string2);
    System.out.println(string3);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

輸出結果

D:save\uploadFile\Java.txt
D:save\uploadFile\java.txt
D:save\uploadFile\java.txt

以上是可以跳過不看的內容


分析

內部實現

  • String:

    String類使用字串陣列儲存字串,因為使用final修飾符,所以可知道String物件是不可變的。所謂的不可變其實是指每次修改都不是在原字元上修改,而是新建了一個新的字元陣列,而且如果這個物件沒有被引用,那這個物件就是沒有用的。

  • StringBuffer:

    繼承了AbstractStringBuilder,而且和String不一樣的是
    String使用的是陣列宣告為 private final char value[];
    StringBuffer的宣告是 private transient char[] toStringCache;
    transient :臨時的
    當StringBuffer進行修改時,(比如說刪除、更新字元)是在原來的例項物件進行修改的。但是如果是拼接操作時,分成兩種情況,1、空間足夠,直接拼接;2、空間不夠,新建了一個字串陣列,再搬家過去。

  • StringBuilder:

    StringBuffer和StringBuffer都是繼承了AbstractStringBuilder,區別只是方法簽名上是否有synchronized。因此,StringBuffer是執行緒安全的,而StringBuilder是執行緒不安全的。
    StringBuffer和StringBuilder類結構圖
    HashTable是執行緒安全的,很多方法都是synchronized方法,而HashMap不是執行緒安全的,但其在單執行緒程式中的效能比HashTable要高。StringBuffer和StringBuilder類的區別也是如此,他們的原理和操作基本相同,區別在於StringBufferd支援併發操作,線性安全的,適 合多執行緒中使用。StringBuilder不支援併發操作,線性不安全的,不適合多執行緒中使用。新引入的StringBuilder類不是執行緒安全的,但其在單執行緒中的效能比StringBuffer高。

執行速度(StringBuilder>StringBuffer>String)

  • String 每次都要新增臨時字串,開銷大,很慢(GC工作壓力大)
  • StringBuffer 建立執行緒安全容器,開銷大,中等
  • StringBuilder 單執行緒推薦使用,執行緒不安全,快
    感覺直接這樣說,你還是不相信,還是覺得使用String多好啊,敲起來還短,所以我就提供了一段程式碼,這段程式碼不是我原創的,但是寫的很好,我就借來修改了一下,程式碼如下
package javaTest;


/**
 *@author CHEN
 *@time 2016年4月15日
 *@about 測試String StringBuffer StringBuilder的效能 
 */
public class StringBuilderTester {
    private static final String base = " base string. ";
    private static final int count = 200000;

    public static void stringTest() {
        long begin, end;
        begin = System.currentTimeMillis();
        String test = new String(base);
        for (int i = 0; i < count ; i++) {
            test = test + " add ";
        }
        end = System.currentTimeMillis();
        System.out.println((end - begin)
                + " millis has elapsed when used String. ");
    }

    public static void stringBufferTest() {
        long begin, end;
        begin = System.currentTimeMillis();
        StringBuffer test = new StringBuffer(base);
        for (int i = 0; i < count; i++) {
            test = test.append(" add ");
        }
        end = System.currentTimeMillis();
        System.out.println((end - begin)
                + " millis has elapsed when used StringBuffer. ");
    }

    public static void stringBuilderTest() {
        long begin, end;
        begin = System.currentTimeMillis();
        StringBuilder test = new StringBuilder(base);
        for (int i = 0; i < count; i++) {
            test = test.append(" add ");
        }
        end = System.currentTimeMillis();
        System.out.println((end - begin)
                + " millis has elapsed when used StringBuilder. ");
    }


    public static void main(String[] args) {
        stringTest();
        stringBufferTest();
        stringBuilderTest();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

執行結果:

113730 millis has elapsed when used String.
13 millis has elapsed when used StringBuffer.
9 millis has elapsed when used StringBuilder.

建議在使用的時候,把count的值乘以10,但stringTest中count縮小100倍,有利於比較StringBuffer和StringBuilder。

執行緒安全

  • String :String是不可變的,所以也就是執行緒安全的
  • StringBuffer:執行緒安全,
  • StringBuilder:執行緒不安全
package hello;

/**
 * @about 對StringBuffer StringBuilder String的執行緒測試
 * @author CHEN
 * @time 2016年4月15日
 */
public class Test {
    public static void main(String[] args) {
        StringBuffer sbf = new StringBuffer();
        StringBuilder sb = new StringBuilder();
        String s=new String();
        //10個執行緒
        for (int i = 0; i < 10; i++) {
            new Thread(new TestThread(sbf, sb, s)).start();
        }
    }
}

class TestThread implements Runnable {
    StringBuffer sbf;
    StringBuilder sb;
    String s;

    TestThread(StringBuffer sbf, StringBuilder sb,String s) {
        this.sb = sb;
        this.sbf = sbf;
        this.s=s;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sb.append("1");
            sbf.append("1");
            s+="1";
            System.out.println(sb.length() + "/" + sbf.length()+"/"+s.length());
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

執行的結果是:

sb很少次能達到1000次
sbf基本都達到1000次
而s 則是100次


後言

  • 如果閱讀String的“+”的位元組碼,其實你就會發現,在底層,系統自動呼叫了StringBilder。
    例如下面的程式碼
public class Buffer {
     public static void main(String[] args) {
        String s1 = "aaaaa";
        String s2 = "bbbbb";
        String r = null;
        int i = 3694;
        r = s1 + i + s2; 

        for(int j=0;i<10;j++){
            r+="23124";
        }
     }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

JVM將會翻譯成
JVM位元組碼
偷偷的呼叫了StringBuilder,為什麼呢?當然是因為快啊。但是別以為JVM幫你做了這部分工作,你就可以濫用String了。String轉成StringBuilder每次都會建立很多的物件的,所以呢,作為一個好的碼農,第一件事就是為JVM多考慮。

總結

就這樣我們認識了String、StringBuffer、StringBuilder三兄弟。
大哥String,雖說頑固不變,但是通用性好,用途廣泛,佔用記憶體小,大眾都喜歡使用它。可是呢,其實大哥String的工作經常是交給小弟StringBuilder做的。
二哥StringBuffer,比大哥通達,改變的時候就會改變。可是,別人讓他辦事,他每次就答應辦一件,每次一件,所以比較可靠安全。
小弟StringBuilder,比較活潑,有時候同時辦好幾件事,就把事給辦壞了。可是呢,小弟他的工作效率是最快的。


相關文章