Java雜談4——Java中的字符串存儲

Java中的String

  Java.Lang.String是Java語言自帶的字符串實現,它並非java的基本類型,但卻和幾乎每一個java程序都密切相關的一個基礎java類。html

  string類內部實際實現存儲的字符數組在定義時用關鍵字final修飾,意味着這個屬性是一個常量,在初始化以後就不能再被修改。這也同時代表全部對String對象的修改操做(包括append,substring,concat,replace,trim等),在具體實現中返回的都是一個全新的string對象副本。總結來講,Java中的String具備不變性、不可繼承性java

討論String對象在內存中的存儲

  這裏會涉及到三個概念:虛擬機棧、Java堆和運行時常量池,在個人第一篇文章中都有描述。根據JDK源碼中的規範,String類的使用方式有以下幾種:c++

    String str1 = new String("abc");

    String str2 = "abc";

    String str3 = "ab" + new String("c");

  在具體應用中,這裏的幾種String對象的建立方式是基本沒有區別的。但實質上這裏有一些微小的差別:第一個字符串的建立方式str1指向的對象被分配到了Java堆中,且建立的時機是在程序運行時。str2指向的字符串對象在編譯期就已經肯定,存放在運行時常量池中。str3的建立是一個比較複雜的過程,java虛擬機會從新組織對應的字節碼,具體的過程在下文會分析。數組

  在學習的過程當中,我也參考了不少前輩的文章,其中包括這篇《Java內存分配和String類型的深度解析》,文中做者在String的定義方法中提出了針對性的幾點疑問,結合我自身的思考,我來嘗試解答一下。安全

  • 堆中new出來的實例和常量池中的是什麼關係?

  二者都是一個String類型的實例化對象,即便經過方法equals比較返回結果爲true,二者在本質上都不會是同一個對象。app

  • 常量池中的字符串常量與堆中的String對象有什麼區別?

  一個最主要的區別就是內存中的位置不一樣,固然大部分的常量池中的字符串常量是在編譯器肯定的,除非很明確的調用string對象的intern方法返回(或建立)一個運行時常量池中的string對象,全部經過new操做符建立的string對象都會被分配到Java堆中。函數

  • 爲何直接定義的字符串一樣能夠調用String對象的各類方法呢?

  雖說字符串常量」abc」是在編譯器被肯定的字符串常量,被存放在運行時常量池,可是這個常量字符串仍是一個String類型的對象(這一點確實有別於c/c++語言中的常量的概念,也看出在java的哲學中萬物都是對象),若是是一個標準的java對象,它能夠調用String的方法。性能

 字節碼分析

  在分析問題的過程當中,經過查看上文中提到的三行java代碼對應的字節碼(方法javap -c {具體要查看的*.class文件}),來確認個人本身的猜測,在這個過程當中也能夠看出java編譯器對源代碼的處置,具體的字節碼分析以下:學習

       //0 ~ 9 對應的是第一行的java語句:String str1 = "abc";
       0: new           #21                 // class java/lang/String
       3: dup           
       /*
       **裝載一個常量字符串 ,符號#23表明的字符串對象就是 「abc」,
       **常量字符串在程序運行以前就已經被建立
       */
       4: ldc           #23                 // String abc
       /*str1不指向常量字符串「abc」,
       **而是將這個常量字符串做爲構造函數的實參傳入
       **在java堆中從新建立了一個全新的對象
       */
       6: invokespecial #25                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
       9: astore_1      

    //從這裏開始到12 都是第二行的Java代碼: String str2 = "abc";
      /*
      ** 直接調用運行時常量池中的對象
      */
      10: ldc           #23                 // String abc
      12: astore_2      

    //今後處開始到最後對應第三行的Java代碼:String str3 = "ab" + new String("c");
      /*
      **對於這種new 對象與 常量字符串相結合的方式,
      **JAVA編譯器在處理過程當中建立了一個StringBuilder對象用於處理異構字符串的拼接工做
      ** "ab"對應另外一個常量字符串 「c」則運行時動態建立的String對象
      **/
      13: new           #28                 // class java/lang/StringBuffer
      16: dup           
      17: ldc           #30                 // String ab
      19: invokespecial #32                 // Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
      22: new           #21                 // class java/lang/String
      25: dup           
      26: ldc           #33                 // String c
      28: invokespecial #25                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
      31: invokevirtual #35                 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
      34: invokevirtual #39                 // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
      37: astore_3      
      38: return            

  由此能夠看出,在處理不一樣方式構建的字符串拼接時,java編譯器爲咱們付出了額外的一些代價,在咱們的代碼中,儘量少出現相似第三行那樣的代碼。若是是肯定的字符串常量,也儘量寫成」ab」 + 「c」這樣的形式,在編譯器優化時會在常量池中找到現成的對象對象,會在性能上有很大的提高。    優化

    同時咱們可以看到編譯器默認用的字符串拼接器是StringBuffuer類,一般狀況下咱們若是須要對幾個動態的string對象作拼接用的都是StringBuilder類。StringBuffer與StringBuilder最本質的區別是:前者是線程安全的。那麼若是隻是明確的單線程環境下,在效率上編譯器自補足的代碼又會有更多性能上的欠缺。

相關文章
相關標籤/搜索