Oo緣來是你oO


          posts - 120,comments - 125,trackbacks - 0

          如何讓你的程序運行的更快(1)---StringVSStringBuffer

          馬嘉楠 2006-09-14

          最開始給這篇文章起名的時候準備叫“如何讓你的程序運行的更快(1)之String StringBuffer誰與爭鋒!”,后來想想武俠的味道太濃了,有吸引眼球騙取點擊率的嫌疑,還是簡單一點的好,畢竟這是技術不是文學,需要嚴謹。你說呢?^+^

          在總體的框架設計確定以后,多注意一些編程細節(jié),積少成多,可以獲得更佳的性能,讓程序跑的更快!

          前些天同事優(yōu)化代碼時提到了String和StringBuffer。仔細想想,發(fā)現自己也就是知道個大概,所以查了 一下資料。文章中有錯誤的地方你可一定要告訴我啊,呵呵^+^

          一.介紹

          String:非可變類(immutable),一旦創(chuàng)建就不能再被改變。
          StringBuffer:可變類,創(chuàng)建之后可以被改變。

          何謂非可變類?
          簡單說,非可變類的實例是不能被修改的,每個實例中包含的信息都必須在該實例創(chuàng)建的時候就提供出來 ,并且在對象的整個生存周期內固定不變。

          非可變類好處:狀態(tài)單一,對象簡單,便于維護;通常是線程安全的;用戶可以共享非可變對象,甚至可 以共享它們的內部信息


          二.創(chuàng)建字符串

          兩種方法:
          1.?String s1 = "hello";
          2.?String s2 = new String("hello");

          哪種方式性能更好?
          例1:

          ?1?//create?String?without?"new"?keyword
          ?2?long?startTime1?= ?System.currentTimeMillis();
          ?3?for(int?i=0;i<100000;i++
          ){
          ?4?????? ?String?str1? = ? " hello "
          ;
          ?5?
          }
          ?6?long?endTime1?=
          ?System.currentTimeMillis();
          ?7?System.out.println("create?String?without?'new'?keyword?:?"?+?(endTime1?-?startTime1)?+?"?milli?seconds"
          ?);
          ?8?
          ?????
          ?9?//create?String?with?'new'?keyword???????

          10?long?startTime2?= ?System.currentTimeMillis();
          11?for(int?i=0;i<100000;i++
          ){
          12???????String?str2?=?new?String("hello"
          );
          13?
          }
          14?long?endTime2?=
          ?System.currentTimeMillis();
          15?System.out.println("create?String?with?'new'?keyword?:?"?+?(endTime2?-?startTime2)?+?"?milli?seconds");


          輸出結果為(注:程序的輸出結果也許和你的結果不同,但是總體趨勢應該是一致的):

          create?String?without? ' new ' ?keyword?:? 0 ?milli?seconds
          create?String?with?
          ' new ' ?keyword?:? 16 ?milli?seconds

          結論: 創(chuàng)建字符串變量時盡可能不使用new關鍵字
          ?
          說明:
          雖然兩個語句都是返回一個String對象的引用,但是jvm對兩者的處理方式是不一樣的。

          對于第一種不用new關鍵字創(chuàng)建String對象:
          JVM首先會在內部維護的滯留字符串中通過String的equels方法查找是對象池中是否存放有該 String對象。如果有,返回已有的String對象給用戶,而不會在heap中重新創(chuàng)建一個新的String對象;如 果對象池中沒有該String對象,JVM則在heap中創(chuàng)建新的String對象,將其引用返回給用戶,同時將該引 用添加至滯留字符串中。

          對于第二種使用new關鍵字創(chuàng)建String對象:
          JVM會馬上在heap中創(chuàng)建一個String對象,然后將該對象的引用返回給用戶。JVM是不會主動把該 對象放到滯留字符串里面的,除非程序調用 String的intern()方法.


          JVM為字符串自變量維護一些唯一的String對象,程序員不需要為字符串自變量而發(fā)愁。但是使用new關鍵 字可能會在內存中創(chuàng)建重復的String對象,你不必為此而煩惱,intern()方法可以幫你解決問題。

          String.intern():檢查字符串對象的存在性,如果需要的字符串對象已經存在,那么它會將引用指向已 經存在的字符串對象而不是重新創(chuàng)建一個。

          例2:

          ?1?String?str3?=?"world";??????/*JVM在滯留字符串中找不到值為“world”的字符串,就在堆上創(chuàng)建一個string對象,并將該對象的引用加入到滯留字符串中*/
          ?2?
          ?3?String?str4?=?new?String("world");??????/*JVM在堆上重新創(chuàng)建一個值為“world”的字符串,此時堆上有兩個值為“world”的字符串*/
          ?4?if(str3?== ?str4){
          ?5???????System.out.println("str3?==?str4"
          );
          ?6?
          }
          ?7?else
          {
          ?8???????System.out.println("str3?!=?str4"
          );
          ?9?
          }
          10?//輸出:str3?!=?str4

          11?
          12?String?str5?=?"world";??????/*JVM在發(fā)現滯留字符串中存在“world”對象,因此返回str3指向的對象給str5,即str3和str5是指向同一個對象的引用*/
          13?
          14?if(str3?== ?str5){
          15???????System.out.println("str3?==?str5"
          );
          16?
          }
          17?else
          {
          18???????System.out.println("str3?!=?str5"
          );
          19?
          }
          20?//輸出:str3?==?str5?????

          21?
          22?str4?=?str4.intern();??????/*此時,JVM發(fā)現滯留字符串中已經存在“world”對象,因此返回str3指向的對象給str4,即str3和str4是指向同一個對象的引用*/
          23? ?????
          24?if(str3?==
          ?str4){
          25???????System.out.println("after?intern()?str3?==?str4"
          );
          26?
          }
          27?else
          {
          28???????System.out.println("after?intern()?str3?!=?str4"
          );
          29?
          }
          30?//輸出:after?intern()?str3?==?str4

          31?
          32?

          結論: 如果使用new關鍵字創(chuàng)建了字符串變量,則盡可能使用intern()方法。

          上面的例子執(zhí)行正是用到了string對象的不可變性質。既然string對象一旦創(chuàng)建就不可以改變,那么多個 引用指向同一個對象就不會對彼此產生影響。


          三.字符串連接

            你可以使用+操作符或者String.concat()或者StringBuffer.append()等辦法來連接多個字符串,哪 一種方法性能最佳?

          如何選擇取決于兩種情景:
          第一種情景:需要連接的字符串是在編譯期間決定的,還是在運行期間決定。
          第二種情景:你使用的是 StringBuffer還是String。

          通常程序員會認為StringBuffer.append()方法會優(yōu)于+操作符或 String.concat()方法,但是在一些特定 的情況下這個假想是不成立的。
          ?

          1) 第一種情景:編譯期間決定VS
          運行期間決定

          ?1?//test?the?string?Concatenation
          ?2?long?startTime6?= ?System.currentTimeMillis();
          ?3?for(int?k=0;?k<100000;?k++
          ){
          ?4???????String?str6?=?"this?is?"?+?"a?test?"?+?"for?string?concatenation"
          ;
          ?5?
          }
          ?6?long?endTime6?=
          ?System.currentTimeMillis();
          ?7?System.out.println("string?concatenation?using?'+'?:?"?+?(endTime6?-?startTime6)?+?"?milli?seconds"
          );
          ?8?
          ??
          ?9?long?startTime7?=
          ?System.currentTimeMillis();
          10?for(int?k=0;?k<100000;?k++
          ){
          11???????String?str7?=?"this?is?"
          ;
          12???????str7.concat("a?test?"
          );
          13???????str7.concat("for?string?concatenation"
          );
          14?
          }
          15?long?endTime7?=
          ?System.currentTimeMillis();
          16?System.out.println("string?concatenation?using?concat()?:?"?+?(endTime7?-?startTime7)?+?"?milli?seconds"
          );
          17?

          18?long?startTime8?= ?System.currentTimeMillis();
          19?for(int?l=0;?l<100000;?l++
          ){
          20???????StringBuffer?sb8?=?new
          ?StringBuffer();
          21???????sb8.append("this?is?"
          );
          22???????sb8.append("a?test?"
          );
          23???????sb8.append("for?string?concatenation"
          );
          24?
          }
          25?long?endTime8?=
          ?System.currentTimeMillis();
          26?System.out.println("string?concatenation?using?append()?:?"?+?(endTime8?-?startTime8)?+?"?milli?seconds");

          上面代碼的輸出結果:

          string?concatenation?using? ' + ' ?:? 0 ?milli?seconds
          string?concatenation?using?concat()?:?
          31
          ?milli?seconds
          string?concatenation?using?append()?:?
          62 ?milli?seconds

          很有趣,+操作符比StringBuffer.append()方法要快. Why?
          ?
          這是因為編譯器對簡單的字符串進行了優(yōu)化。即使使用new關鍵字來創(chuàng)建String對象的時候也是如此。例 如,

          編譯前:
          String str6 = "this is " + "a test " + "for string concatenation";
          編譯后:
          String str6 = "this is a test for string concatenation";

          這里String對象在編譯期間就決定了而StringBuffer對象是在運行期間決定的。運行期間決定需要額外的 開銷。

          結論: 如果字符串在編譯期間就可以決定它的值,則字符串拼接的時候, “+”操作符效率更高,簡單的 認為append()效率高于“+”是錯誤的。
          ?

          2) 第二種情景:StringBufferVS
          String

          ?1?//string?concatenation?using?'+='
          ?2?long?startTime9?= ?System.currentTimeMillis();
          ?3?String?str9?=?"hello"
          ;
          ?4?for(int?i=0;?i<10000;?i++
          ){
          ?5???????str9?+=?"hello"
          ;
          ?6?
          }
          ?7?long?endTime9?=
          ?System.currentTimeMillis();
          ?8?System.out.println("string?concatenation?using?'+='?:?"?+?(endTime9?-?startTime9)?+?"?milli?seconds"
          );
          ?9?
          ??
          10?//string?concatenation?using?append()

          11?long?startTime10?= ?System.currentTimeMillis();
          12?StringBuffer?sb10?=?new?StringBuffer("hello"
          );
          13?for(int?i=0;?i<10000;?i++
          ){
          14???????sb10.append("hello"
          );
          15?
          }
          16?long?endTime10?=
          ?System.currentTimeMillis();
          17?System.out.println("string?concatenation?using?append()?:?"?+?(endTime10?-?startTime10)?+?"?milli?seconds");


          上面代碼的輸出結果:

          string?concatenation?using? ' += ' ?:? 3094 ?milli?seconds
          string?concatenation?using?append()?:?
          16 ?milli?seconds

          結論: 避免使用“+=”來構造字符串

          雖然兩者都是在運行期間決定字符串對象,但是使用+=操作符會產生更多的臨時對象。

          在上例中,由于String類是不可變的,所以進行字符串拼接的時候,每循環(huán)一次就會產生臨時對象來保存 str9和“hello”的值,之后創(chuàng)建一個臨時的StringBuffer對象,并調用其append()方法來完成字符串的 拼接,最后調用toString()方法,將臨時StringBuffer對象轉為String再賦值給str9。此時str9已經改變 ,指向了新的對象。


          3) 第三種情景:設置StringBuffer的容量來提升性能

          ?1?long?startTime10?= ?System.currentTimeMillis();
          ?2?StringBuffer?sb10?=?new?StringBuffer("hello"
          );
          ?3?for(int?i=0;?i<10000;?i++
          ){
          ?4???????sb10.append("hello"
          );
          ?5?
          }
          ?6?long?endTime10?=
          ?System.currentTimeMillis();
          ?7?System.out.println("string?concatenation?using?append()?:?"?+?(endTime10?-?startTime10)?+?"?milli?seconds"
          );
          ?8?
          ??
          ?9?//set?the?StringBuffer?capacity

          10?long?startTime11?= ?System.currentTimeMillis();
          11?StringBuffer?sb11?=?new?StringBuffer("hello"
          );
          12?sb11.ensureCapacity(10000
          );
          13?for(int?i=0;?i<10000;?i++
          ){
          14???????sb11.append("hello"
          );
          15?
          }
          16?long?endTime11?=
          ?System.currentTimeMillis();
          17?System.out.println("string?concatenation?using?append()?after?set?the?StringBuffer?capacity?:?"?+?(endTime11?-?startTime11)?+?"?milli?seconds");


          ? 上面代碼的輸出結果:

          string?concatenation?using?append()?:? 16 ?milli?seconds
          string?concatenation?using?append()?after?set?the?StringBuffer?capacity?:?
          0 ?milli?seconds

          結論:
          聲明StringBuffer對象的時候,指定合適的capacity,會提升程序性能。

          1)使用StringBuffer的構造函數來設定它的初始化容量:StringBuffer(int length)
          2)使用ensureCapacity(int minimumcapacity)方法在StringBuffer對象創(chuàng)建之后設置它的容量。

          首先我們看看StringBuffer的缺省行為,然后再找出一條更好的提升性能的途徑。
          ?
          StringBuffer的缺省行為:
            StringBuffer在內部維護一個字符數組,當你使用缺省的構造函數來創(chuàng)建StringBuffer對象的時候, StringBuffer的容量被初始化為16個字符,也就是說缺省容量就是16個字符。當StringBuffer達到最大容 量的時候,它會將自身容量增加到當前的2倍再加2,也就是(2*舊值+2)。
            如果你使用缺省值,初始化之后接著往里面追加字符,在你追加到第17(原文是16,其實是錯誤的,因為在追加到第16個字符的時候,容量不會發(fā)生變化,很抱歉,以后會更嚴謹一些^+^)個字符的時候它會將容量增加 到34(2*16+2),當追加到34個字符的時候就會將容量增加到70(2*34+2)。無論何事只要StringBuffer 到達它的最大容量它就不得不創(chuàng)建一個新的字符數組然后重新將舊字符和新字符都拷貝一遍。所以給 StringBuffer設置一個合理的初始化容量值,會提升程序的性能。

          但是為什么容量變化的時候是2*舊值+2呢?有誰能告訴我么?查資料的時候沒有找到

          ?

          附:查資料的過程中發(fā)現了jdk 5.0還提供了StringBuilder類,我是沒有用過。不過也介紹一下好了。( 來源 JavaWorld ):

          ?????? Java.lang.StringBuffer 線程安全的可變字符序列。類似于 String 的字符串緩沖區(qū),但不能修 改。可將字符串緩沖區(qū)安全地用于多個線程。可以在必要時對這些方法進行同步,因此任意特定實例上的 所有操作就好像是以串行順序發(fā)生的,該順序與所涉及的每個線程進行的方法調用順序一致。

          ?????? 每個字符串緩沖區(qū)都有一定的容量。只要字符串緩沖區(qū)所包含的字符序列的長度沒有超出此容量 ,就無需分配新的內部緩沖區(qū)數組。如果內部緩沖區(qū)溢出,則此容量自動增大。從 JDK 5.0 開始,為該 類增添了一個單個線程使用的等價類,即 StringBuilder 。與該類相比,通常應該優(yōu)先使用 StringBuilder 類,因為它支持所有相同的操作,但由于它不執(zhí)行同步,所以速度更快。 但是如果將 StringBuilder 的實例用于多個線程是不安全的。需要這樣的同步,則建議使用 StringBuffer 。


          ?



          馬嘉楠
          jianan.ma@gmail.com

          posted on 2006-09-14 17:25 馬嘉楠 閱讀(3045) 評論(6)  編輯  收藏

          FeedBack:
          # re: 如何讓你的程序運行的更快(1)---String VS StringBuffer
          2006-09-15 09:24 | Liam
          不錯,很好,收益了~~期待下次的內容  回復  更多評論
            
          # re: 如何讓你的程序運行的更快(1)---String VS StringBuffer
          2006-09-15 11:21 | themax
          不錯.謝謝!  回復  更多評論
            
          # re: 如何讓你的程序運行的更快(1)---String VS StringBuffer
          2006-09-15 12:01 | 吳加前
          我用過StringBuilder這個類,在程序中確定沒有用到多線程的時候,用StringBuilder比StringBuffer的效率要高.  回復  更多評論
            
          # re: 如何讓你的程序運行的更快(1)---String VS StringBuffer
          2006-09-15 12:03 | xinheqishi
          收益良多,謝謝了。  回復  更多評論
            
          # re: 如何讓你的程序運行的更快(1)---String VS StringBuffer
          2006-09-19 11:48 | 盧翔
          牛X,收了  回復  更多評論
            
          # re: 如何讓你的程序運行的更快(1)---String VS StringBuffer
          2006-09-22 20:43 | ljcyu
          第一個測試有問題 ,你運行的次數太少了,很受操作系統(tǒng)當時狀況的影響 。最好是運行1000次,然后取平均數,我這里得到的一個是3.734,一個是1.157。   回復  更多評論
            

          只有注冊用戶登錄后才能發(fā)表評論。


          網站導航:
           
          主站蜘蛛池模板: 寿阳县| 当阳市| 荣成市| 交口县| 蒙自县| 华宁县| 潞西市| 安阳县| 海宁市| 南投市| 乳源| 晋宁县| 内黄县| 建德市| 平凉市| 株洲市| 康平县| 潮州市| 方正县| 咸宁市| 贵德县| 襄汾县| 广饶县| 通山县| 海晏县| 海城市| 厦门市| 明光市| 浮梁县| 淳安县| 沂源县| 婺源县| 辽源市| 柳林县| 板桥市| 大渡口区| 苍溪县| 涟水县| 怀安县| 贵州省| 塔河县|