Java基礎-String、StringBuffer、StringBuilder

來源:互聯網
上載者:User

標籤:

 

看下面這段代碼:

public class Main {             public static void main(String[] args) {        String string = "";        for(int i=0;i<10000;i++){            string += "hello";        }    }}

這句 string += "hello";的過程相當於將原有的string變數指向的對象內容取出與"hello"作字串相加操作再存進另一個新的String對象當中,再讓string變數指向新產生的對象。

從這段反編譯出的位元組碼檔案可以很清楚地看出:從第8行開始到第35行是整個迴圈的執行過程,並且每次迴圈會new出一個StringBuilder對象,然後進行append操作,最後通過toString方法返回String對象。也就是說這個迴圈執行完畢new出了10000個對象,試想一下,如果這些對象沒有被回收,會造成多大的記憶體資源浪費。從上面還可以看出:string+="hello"的操作事實上會自動被JVM最佳化成:

StringBuilder str = new StringBuilder(string);str.append("hello");str.toString();

 

再看下面這段代碼:

public class Main {             public static void main(String[] args) {        StringBuilder stringBuilder = new StringBuilder();        for(int i=0;i<10000;i++){            stringBuilder.append("hello");        }    }}

反編譯位元組碼檔案得到:

從這裡可以明顯看出,這段代碼的for迴圈式從13行開始到27行結束,並且new操作只進行了一次,也就是說只產生了一個對象,append操作是在原有對象的基礎上進行的。因此在迴圈了10000次之後,這段代碼所佔的資源要比上面小得多。

 

那麼有人會問既然有了StringBuilder類,為什麼還需要StringBuffer類?查看原始碼便一目瞭然,事實上,StringBuilder和StringBuffer類擁有的成員屬性以及成員方法基本相同,區別是StringBuffer類的成員方法前面多了一個關鍵字:synchronized,不用多說,這個關鍵字是在多線程訪問時起到安全保護作用的,也就是說StringBuffer是安全執行緒的。

 

下面摘了2段代碼分別來自StringBuffer和StringBuilder,insert方法的具體實現:

 

StringBuilder的insert方法

public StringBuilder insert(int index, char str[], int offset,                              int len)  {      super.insert(index, str, offset, len);    return this;  }

StringBuffer的insert方法:

public synchronized StringBuffer insert(int index, char str[], int offset,                                            int len)    {        super.insert(index, str, offset, len);        return this;    }

 

 

 

 

 

效能測試
public class Main {    private static int time = 50000;    public static void main(String[] args) {        testString();        testStringBuffer();        testStringBuilder();        test1String();        test2String();    }              public static void testString () {        String s="";        long begin = System.currentTimeMillis();        for(int i=0; i<time; i++){            s += "java";        }        long over = System.currentTimeMillis();        System.out.println("操作"+s.getClass().getName()+"類型使用的時間為:"+(over-begin)+"毫秒");    }         public static void testStringBuffer () {        StringBuffer sb = new StringBuffer();        long begin = System.currentTimeMillis();        for(int i=0; i<time; i++){            sb.append("java");        }        long over = System.currentTimeMillis();        System.out.println("操作"+sb.getClass().getName()+"類型使用的時間為:"+(over-begin)+"毫秒");    }         public static void testStringBuilder () {        StringBuilder sb = new StringBuilder();        long begin = System.currentTimeMillis();        for(int i=0; i<time; i++){            sb.append("java");        }        long over = System.currentTimeMillis();        System.out.println("操作"+sb.getClass().getName()+"類型使用的時間為:"+(over-begin)+"毫秒");    }         public static void test1String () {        long begin = System.currentTimeMillis();        for(int i=0; i<time; i++){            String s = "I"+"love"+"java";        }        long over = System.currentTimeMillis();        System.out.println("字串直接相加操作:"+(over-begin)+"毫秒");    }         public static void test2String () {        String s1 ="I";        String s2 = "love";        String s3 = "java";        long begin = System.currentTimeMillis();        for(int i=0; i<time; i++){            String s = s1+s2+s3;        }        long over = System.currentTimeMillis();        System.out.println("字串間接相加操作:"+(over-begin)+"毫秒");    }     }

 

結果

上面提到string+="hello"的操作事實上會自動被JVM最佳化,看下面這段代碼:

public class Main {    private static int time = 50000;    public static void main(String[] args) {        testString();        testOptimalString();    }              public static void testString () {        String s="";        long begin = System.currentTimeMillis();        for(int i=0; i<time; i++){            s += "java";        }        long over = System.currentTimeMillis();        System.out.println("操作"+s.getClass().getName()+"類型使用的時間為:"+(over-begin)+"毫秒");    }         public static void testOptimalString () {        String s="";        long begin = System.currentTimeMillis();        for(int i=0; i<time; i++){            StringBuilder sb = new StringBuilder(s);            sb.append("java");            s=sb.toString();        }        long over = System.currentTimeMillis();        System.out.println("類比JVM最佳化操作的時間為:"+(over-begin)+"毫秒");    }     }

 

得到驗證。

  下面對上面的執行結果進行一般性的解釋:

1)對於直接相加字串,效率很高,因為在編譯器便確定了它的值,也就是說形如"I"+"love"+"java"; 的字串相加,在編譯期間便被最佳化成了"Ilovejava"。這個可以用javap -c命令反編譯產生的class檔案進行驗證。

  對於間接相加(即包含字串引用),形如s1+s2+s3; 效率要比直接相加低,因為在編譯器不會對引用變數進行最佳化。

2)String、StringBuilder、StringBuffer三者的執行效率:  

StringBuilder > StringBuffer > String

 

當然這個是相對的,不一定在所有情況下都是這樣。

比如String str = "hello"+ "world"的效率就比 StringBuilder st  = new StringBuilder().append("hello").append("world")要高。

  因此,這三個類是各有利弊,應當根據不同的情況來進行選擇使用:

  當字串相加操作或者改動較少的情況下,建議使用 String str="hello"這種形式;

  當字串相加操作較多的情況下,建議使用StringBuilder,如果採用了多線程,則使用StringBuffer。

 

關於String、StringBuffer的面試題

1. 下面這段代碼的輸出結果是什嗎?

String a = "hello2";   String b = "hello" + 2;   System.out.println((a == b));

 

  輸出結果為:true。原因很簡單,"hello"+2在編譯期間就已經被最佳化成"hello2",因此在運行期間,變數a和變數b指向的是同一個對象。

 

2.下面這段代碼的輸出結果是什嗎?  

String a = "hello2";    String b = "hello";       String c = b + 2;       System.out.println((a == c));

 

  輸出結果為:false。由於有符號引用的存在,所以  String c = b + 2;不會在編譯期間被最佳化,不會把b+2當做字面常量來處理的,因此這種方式產生的對象事實上是儲存在堆上的。因此a和c指向的並不是同一個對象。javap -c得到的內容:

 

3.下面這段代碼的輸出結果是什嗎?

String a = "hello2";     final String b = "hello";       String c = b + 2;       System.out.println((a == c));

 

輸出結果為:true。對於被final修飾的變數,會在class檔案常量池中儲存一個副本,也就是說不會通過串連而進行訪問,對final變數的訪問在編譯期間都會直接被替代為真實的值。那麼String c = b + 2;在編譯期間就會被最佳化成:String c = "hello" + 2; 

 

4.下面這段代碼輸出結果為:

public class Main {    public static void main(String[] args) {        String a = "hello2";        final String b = getHello();        String c = b + 2;        System.out.println((a == c));    }         public static String getHello() {        return "hello";    }}

 

輸出結果為false。這裡面雖然將b用final修飾了,但是由於其賦值是通過方法調用返回的,那麼它的值只能在運行期間確定,因此a和c指向的不是同一個對象。

 

 

5.下面這段代碼的輸出結果是什嗎?

public class Main {    public static void main(String[] args) {        String a = "hello";        String b =  new String("hello");        String c =  new String("hello");        String d = b.intern();                 System.out.println(a==b);        System.out.println(b==c);        System.out.println(b==d);        System.out.println(a==d);    }}

 

這裡面涉及到的是String.intern方法的使用。在String類中,intern方法是一個本地方法,在JAVA SE6之前,intern方法會在運行時常量池中尋找是否存在內容相同的字串,如果存在則返回指向該字串的引用,如果不存在,則會將該字串入池,並返回一個指向該字串的引用。因此,a和d指向的是同一個對象。

 

6.String str = new String("abc")建立了多少個對象

這個問題自身就沒有合理的答案,大部分網上流傳的以及一些面試書籍上都說是2個對象(一個是“xyz”,一個是指向“xyz”的引用對象s),這種說法是片面的。

這問題的毛病是什麼呢?它並沒有定義“建立了”的意義。 
什麼叫“建立了”?什麼時候建立了什嗎? 
而且這段Java程式碼片段實際啟動並執行時候真的會“建立兩個String執行個體”嗎?

如果這道是面試題,那麼可以當面讓面試官澄清“建立了”的定義,然後再對應的回答。這種時候面試官多半會讓被面試者自己解釋,那就好辦了,好好曬給面試官看。 

如果是筆試題就沒有提問要求澄清的機會了。不過會出這種題目的地方水平多半也不怎麼樣。說不定出題的人就是從各種寶典上把題抄來的,那就按照寶典把那不大對勁的答案寫上去就能混過去了

 

先換成另一個問題來問:

String s = new String("xyz");  在運行時涉及幾個String執行個體?

一種合理的解答是:

兩個,一個是字串字面量"xyz"所對應的、駐留(intern)在一個全域共用的字串常量池中的執行個體,另一個是通過new String(String)建立並初始化的、內容與"xyz"相同的執行個體

也就是規定了Java語言一般是編譯為Java虛擬機器規範所定義的Class檔案,但並沒有規定“一定”(must),留有不使用JVM來實現Java語言的餘地。 
考慮上Java虛擬機器規範,確實在這段代碼裡涉及的常量種類為CONSTANT_String_info的字串常量也只有"xyz"一個。CONSTANT_String_info是用來表示Java語言中String類型的常量運算式的值(包括字串字面量)用的常量種類,只在這個層面上考慮的話,這個解答也沒問題。 
所以這種解答可以認為是合理的。 

值得注意的是問題中“在運行時”既包括類載入階段,也包括這個程式碼片段自身執行的時候。

 

String s = new String("xyz");  涉及使用者聲明的幾個String類型的變數?

一個,就是String s。

 

String s = null;  涉及使用者聲明的幾個String類型的變數?

Java裡變數就是變數,參考型別的變數只是對某個對象執行個體或者null的引用,不是執行個體本身。聲明變數的個數跟建立執行個體的個數沒有必然關係,像是說: 
String s1 = "a";  String s2 = s1.concat("");  String s3 = null;  new String(s1);  

這段代碼會涉及3個String類型的變數, 
1、s1,指向下面String執行個體的1 
2、s2,指向與s1相同 
3、s3,值為null,不指向任何執行個體

以及3個String執行個體, 
1、"a"字面量對應的駐留的字串常量的String執行個體 
2、""字面量對應的駐留的字串常量的String執行個體 
(String.concat()是個有趣的方法,當發現傳入的參數是Null 字元串時會返回this,所以這裡不會額外建立新的String執行個體) 
3、通過new String(String)建立的新String執行個體;沒有任何變數指向它。

....具體討論查閱http://rednaxelafx.iteye.com/blog/774673/

 

 

7.下面這段代碼1)和2)的區別是什嗎?

 

public class Main {    public static void main(String[] args) {        String str1 = "I";        //str1 += "love"+"java";        1)        str1 = str1+"love"+"java";      //2)             }}

1)的效率比2)的效率要高,1)中的"love"+"java"在編譯期間會被最佳化成"lovejava",而2)中的不會被最佳化。

 

Java基礎-String、StringBuffer、StringBuilder

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.