深刻理解 String, StringBuffer 與 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 會規規矩矩的按照原來的方式去作html


在大部分狀況下StringBuffer > String
StringBufferjava

Java.lang.StringBuffer線程安全的可變字符序列。一個相似於 String 的字符串緩衝區,但不能修改。雖然在任意時間點上它都包含某種特定的字符序列,但經過某些方法調用能夠改變該序列的長度和內容。程序員

每一個字符串緩衝區都有必定的容量。只要字符串緩衝區所包含的字符序列的長度沒有超出此容量,就無需分配新的內部緩衝區數組。若是內部緩衝區溢出,則此容量自動增大。從 JDK 5.0 開始,爲該類增添了一個單個線程使用的等價類,即 StringBuilder 。與該類相比,一般應該優先使用 StringBuilder 類,由於它支持全部相同的操做,但因爲它不執行同步,因此速度更快。編程

可將字符串緩衝區安全地用於多個線程。能夠在必要時對這些方法進行同步,所以任意特定實例上的全部操做就好像是以串行順序發生的,該順序與所涉及的每一個線程進行的方法調用順序一致。
StringBuffer上的主要操做是 append 和 insert 方法,可重載這些方法,以接受任意類型的數據。每一個方法都能有效地將給定的數據轉換成字符串,而後將該字符串的字符追加或插入到字符串緩衝區中。append 方法始終將這些字符添加到緩衝區的末端;而 insert 方法則在指定的點添加字符。
例如,若是 z 引用一個當前內容是「start」的字符串緩衝區對象,則此方法調用 z.append("le") 會使字符串緩衝區包含「startle」,而 z.insert(4, "le") 將更改字符串緩衝區,使之包含「starlet」。
在大部分狀況下StringBuilder StringBuffer數組

java.lang.StringBuilder緩存

java.lang.StringBuilder一個可變的字符序列是5.0新增的。此類提供一個與StringBuffer兼容的 API,但不保證同步。類被設計用做StringBuffer的一個簡易替換,用在字符串緩衝區被單個線程使用的時候(這種狀況很廣泛)。若是可能,建議優先採用該類,由於在大多數實現中,它比StringBuffer要快。二者的方法基本相同。安全

可是若是將 StringBuilder 的實例用於多個線程是不安全的。須要這樣的同步,則建議使用 StringBuffer 。性能優化

ok,Talk is cheap,show you the code:app

package com.test;

public class Testssb {
	   
    /** Creates a new instance of testssb */
    final static int ttime = 70000;// 測試循環次數
    public Testssb() {
    }
   
    public void test(String s){
        long begin = System.currentTimeMillis();
        for(int i=0;i<ttime;i++){
            s += "add";
        }
        long over = System.currentTimeMillis();
        System.out.println(" 操做 "+s.getClass().getName()+" 類型使用的時間爲: "
            + (over - begin) + " 毫秒 " );       
    }
    public void test(StringBuffer s){
        long begin = System.currentTimeMillis();
        for(int i=0;i<ttime;i++){
            s.append("add");
        }
        long over = System.currentTimeMillis();
        System.out.println(" 操做 "+s.getClass().getName()+" 類型使用的時間爲: "
            + (over - begin) + " 毫秒 " );       
    }
    public void test(StringBuilder s){
        long begin = System.currentTimeMillis();
        for(int i=0;i<ttime;i++){
            s.append("add");
        }
        long over = System.currentTimeMillis();
        System.out.println(" 操做 "+s.getClass().getName()+" 類型使用的時間爲: "
            + (over - begin) + " 毫秒 " );       
    }
    // 對 String 直接進行字符串拼接的測試
    public void test2(){
        String s2 = "abadf";
        long begin = System.currentTimeMillis();
        for(int i=0;i<ttime;i++){
            String s = s2 + s2 + s2 ;
        }
        long over = System.currentTimeMillis();
        System.out.println(" 操做字符串對象引用相加類型使用的時間爲: "
            + (over - begin) + " 毫秒 " );       
    }
    public void test3(){
        long begin = System.currentTimeMillis();
        for(int i=0;i<ttime;i++){
            String s = "abadf" + "abadf" + "abadf" ;
        }
        long over = System.currentTimeMillis();
        System.out.println(" 操做字符串相加使用的時間爲: "
            + (over - begin) + " 毫秒 " );       
    }
   
    public static void main(String[] args){
    String s1 ="abc";
    StringBuffer sb1 = new StringBuffer("abc");
    StringBuilder sb2 = new StringBuilder("abc");
    Testssb t = new Testssb();
    t.test(s1);
    t.test(sb1);
    t.test(sb2);
    t.test2();
    t.test3();
    }
}

測試結果:jvm

 50000
 
 操做 java.lang.String 類型使用的時間爲: 10456 毫秒 
 操做 java.lang.StringBuffer 類型使用的時間爲: 4 毫秒 
 操做 java.lang.StringBuilder 類型使用的時間爲: 3 毫秒 
 操做字符串對象引用相加類型使用的時間爲: 23 毫秒 
 操做字符串相加使用的時間爲: 1 毫秒 
 
 60000
 操做 java.lang.String 類型使用的時間爲: 17455 毫秒 
 操做 java.lang.StringBuffer 類型使用的時間爲: 4 毫秒 
 操做 java.lang.StringBuilder 類型使用的時間爲: 3 毫秒 
 操做字符串對象引用相加類型使用的時間爲: 13 毫秒 
 操做字符串相加使用的時間爲: 1 毫秒 

 70000
 操做 java.lang.String 類型使用的時間爲: 25882 毫秒 
 操做 java.lang.StringBuffer 類型使用的時間爲: 5 毫秒 
 操做 java.lang.StringBuilder 類型使用的時間爲: 3 毫秒 
 操做字符串對象引用相加類型使用的時間爲: 14 毫秒 
 操做字符串相加使用的時間爲: 1 毫秒

你還能夠往下繼續加大測試數據,只是個人本子扛不住了。。。就不測了。。。

其實我這裏測試並非很公平,由於都放在了一塊兒以前後順序進行,測試方法中間沒有考慮到JVM的GC收集前面產生的無引用對象垃圾而對執行過程的中斷時間。若是你們有更好的想法或者思路歡迎跟我討論:decli AT qq DOT com

關於上文所述:在 JVM 眼裏,這個 

String S1 = 「This is only a」 + 「 simple」 + 「test」; 其實就是: 

String S1 = 「This is only a simple test」; 
這裏的表述不恰當 
String S1 = 「This is only a」 + 「 simple」 + 「test」; 其實就是: 

String S1 = 「This is only a simple test」; 這麼說是對的,可是若是是jvm的把戲,那麼String S1 = 「This is only a」 + 「 simple」 + 「test」; 是否是會和 
String S3=「This is only a」; 
S3+=「 simple」; 
S3+=「test」; 
System.out.println(S1==S3);的結果是同樣呢?很顯然結果是false; 

其實這並非JVM的把戲,而是java編譯器的把戲。 
給出以下源代碼 

package com.test;

public class T {
	
	public static void main(String[] args) {
		String s1 = "This is only a" + " simple " + "test"; 
		String s2 = "This is only a"; 
		s2+= " simple "; 
		s2+= "test"; 
		System.out.println(s1==s2); 
	}
}

那麼它編譯(JDK5.0)後的class會是什麼樣的呢:

H:\tmp_download>jad -p T.class
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name:   T.java

package com.test;

import java.io.PrintStream;

public class T
{

    public T()
    {
    }

    public static void main(String args[])
    {
        String s1 = "This is only a simple test";
        String s2 = "This is only a";
        s2 = (new StringBuilder(String.valueOf(s2))).append(" simple ").toString();
        s2 = (new StringBuilder(String.valueOf(s2))).append("test").toString();
        System.out.println(s1 == s2);
    }
}

H:\tmp_download>

能夠看到 + 變成了 StringBuilder 的方式,這是Javac的問題,也是Javac優化class的一個技巧。  

http://stackoverflow.com/questions/8725739/stringbuilder-usage

注:不要用 Java decompiler 去編譯,這個工具會作一些美化,而要用 JAD:

JAVA高級:反編譯工具jad的簡單用法

http://tech.sina.com.cn/s/2007-09-28/09061768599.shtml

http://www.varaneckas.com/jad/

最後,咱們再來看個常見的問題:

new String() vs literal string(字面量

Java運行環境有一個字符串常量池(String constant pool),由String類維護。執行語句String s="abc"時,首先查看字符串池中是否存在字符串"abc",若是存在則直接將"abc"賦給s,若是不存在則先在字符串池中新建一個字符串"abc",而後再將其賦給s。
執行語句String s=new String("abc")時,在運行時涉及 2 個String實例,一個是字符串字面量"xyz"所對應的、駐留(intern)在一個全局共享的字符串常量池中的實例,另外一個是經過new String(String)建立並初始化的、內容與"xyz"相同的實例。
前一語句的效率高,後一語句的效率低,由於新建字符串須要佔用內存空間和時間。

注意如下區別:

Also, a little test you can do to drive the point home:

String a = new String("xpto");
String b = new String("xpto");
String c = "xpto";
String d = "xpto";
String e = a.intern();

System.out.println(a == b);
System.out.println(a == c);
System.out.println(c == d);
System.out.println(c == e);
With all this, you can probably figure out the results of these Sysouts:

false
false
true
true
Since c and d are the same object, the == comparison holds true.

intern()

對於上面使用new建立的字符串對象,若是想將這個對象的引用加入到字符串常量池,可使用intern方法。

調用intern後,首先檢查字符串常量池中是否有該對象的引用,若是存在,則將這個引用返回給變量,不然將引用加入並返回給變量。

http://stackoverflow.com/questions/14757978/new-string-vs-literal-string-performance

http://rednaxelafx.iteye.com/blog/774673

http://zhidao.baidu.com/question/26614057.html

http://developer.51cto.com/art/201106/266454.htm  Java中的String與常量池

http://www.iteye.com/topic/634530  Java堆.棧和常量池 筆記

http://droidyue.com/blog/2014/12/21/string-literal-pool-in-java/?comefrom=http://blogread.cn/news/  Java中的字符串常量池

REF:

是 String , StringBuffer 仍是 StringBuilder ?

http://www.blogjava.net/chenpengyi/archive/2006/05/04/44492.html

http://baike.baidu.com/view/3645996.htm

深刻理解Java中的String

http://g21121.iteye.com/blog/1873262

爲何String類是不可變的? 

http://www.importnew.com/7440.html

JDK6 和 JDK7 中的 substring() 實現差別(JDK6和JDK7中的substring()方法)

http://www.importnew.com/7418.html

Java中的String對象是不可變的嗎 

http://www.importnew.com/9468.html

Oracle優化Java字符串內部表示

http://www.infoq.com/cn/news/2013/12/Oracle-Tunes-Java-String

如何寫一個不可變類? 

http://www.importnew.com/7535.html

深刻理解Java中的final關鍵字 

http://www.importnew.com/7553.html

Java 性能優化之 String 篇

http://www.ibm.com/developerworks/cn/java/j-lo-optmizestring/

Integer.valueOf(String) 方法之惑 

http://www.importnew.com/9162.html

public static void main(String[] args) throws IOException {
		System.out.println(Integer.valueOf(127)==Integer.valueOf(127));
		System.out.println(Integer.valueOf("127")==Integer.valueOf("127"));
		System.out.println(Integer.valueOf("128")==Integer.valueOf("128"));
		System.out.println(Integer.parseInt("128")==Integer.valueOf("128"));
	}

//
true
false
false
true

Java中關於String類型的10個問題 

http://www.importnew.com/12845.html

什麼是字符串常量池? 

http://www.importnew.com/10756.html

Java程序員們最常犯的10個錯誤 

http://www.importnew.com/12074.html

Java編程提升性能時需注意的地方

http://blog.jobbole.com/16474/

儘可能使用基本數據類型代替對象:
String str = "hello";
上面這種方式會建立一個「hello」字符串,並且JVM的字符緩存池還會緩存這個字符串;
String str = new String("hello");
此時程序除建立字符串外,str所引用的String對象底層還包含一個char[]數組,這個char[]數組依次存放了h,e,l,l,o

Java中的substring真的會引發內存泄露麼?

http://droidyue.com/blog/2014/12/14/substring-memory-issue-in-java/

淺談StringBuilder

「for循環中使用 "+" 拼接爲何這麼慢」:用 "+" 進行拼接,都會轉化成 StringBuilder 對象,可是在for循環中,每循環一次,就建立一個StringBuilder 對象,若是循環1千萬次,就建立了1千萬個StringBuilder 對象,性能會差不少。

http://www.jianshu.com/p/160c9be0b132

相關文章
相關標籤/搜索