子非魚

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            21 Posts :: 0 Stories :: 1 Comments :: 0 Trackbacks

          經常看到很多人討論java中關于String的問題,我也就有點興趣了,鑒于網上很多人寫的都差別很大,
          同樣的問題都是不同的說法,我很迷糊,花了一晚上讀了Java Virtual Machine Specification和
          The Java Language Specification的一些章節,做了很多試驗,總結了一下關于String的內容,還
          有很多內容我也不確定,在下面也都提出來了,希望高手能指正.


          Constant Pool常量池的概念:

          在講到String的一些特殊情況時,總會提到String Pool或者Constant Pool,但是我想很多人都不太
          明白Constant Pool到底是個怎么樣的東西,運行的時候存儲在哪里,所以在這里先說一下Constant Pool的內容.
          String Pool是對應于在Constant Pool中存儲String常量的區域.習慣稱為String Pool,也有人稱為
          String Constant Pool.好像沒有正式的命名??

          在java編譯好的class文件中,有個區域稱為Constant Pool,他是一個由數組組成的表,類型
          為cp_info constant_pool[],用來存儲程序中使用的各種常量,包括Class/String/Integer等各
          種基本Java數據類型,詳情參見The Java Virtual Machine Specification 4.4章節.


          對于Constant Pool,表的基本通用結構為:

          cp_info {
                  u1 tag;
                  u1 info[];
          }

           


          tag是一個數字,用來表示存儲的常量的類型,例如8表示String類型,5表示Long類型,info[]根據
          類型碼tag的不同會發生相應變化.

          對于String類型,表的結構為:

          CONSTANT_String_info {
                  u1 tag;
                  u2 string_index;
          }

           

          tag固定為8,string_index是字符串內容信息,類型為:

          CONSTANT_Utf8_info {
                  u1 tag;
                  u2 length;
                  u1 bytes[length];
          }

           

          tag固定為1,length為字符串的長度,bytes[length]為字符串的內容.

          (以下代碼在jdk6中編譯)
          為了詳細理解Constant Pool的結構,我們參看一些代碼:

              String s1 = "sss111";
              String s2 
          = "sss222";
              System.out.println(s1 
          + " " + s2);

           

          由于"sss111"和"sss222"都是字符串常量,在編譯期就已經創建好了存儲在class文件中.
          在編譯后的class文件中會存在這2個常量的對應表示:
          08 00 11 01 00 06 73 73 73 31 31 31 ; ..sss111
          08 00 13 01 00 06 73 73 73 32 32 32 ; ..sss222

          根據上面說的String常量結構,我們分析一下
          開始的08為CONSTANT_String_info結構中的tag,而11應該是它的相對引用,01為
          CONSTANT_Utf8_info的tag,06為對應字符串的長度,73 73 73 31 31 31為字符串對
          應的編碼,接著分析,會發現后面的是對應"sss222"的存儲結構.


          經過上面分析,我們知道了11和13是兩個字符串的相對引用,就可以修改class文件
          來修改打印的內容,把class文件中的
          00 6E 00 04 00 03 00 00 00 24 12 10 4C 12 12 4D
          改成
          00 6E 00 04 00 03 00 00 00 24 12 10 4C 12 10 4D
          程序就會輸出sss111 sss111,而不是和原程序一樣輸出sss111 sss222,因為我
          們把對"sss222"的相對引用12改成了對"sss111"的相對引用10.


          ------------分割線

          public class Test {
              
          public static void main(String[] args) {
                  String s1 
          = "sss111";
                  String s2 
          = "sss111";
              }

          }

           

          在上面程序中存在2個相同的常量"sss111",對于n個值相同的String常量,在Constant Pool中
          只會創建一個,所以在編譯好的class文件中,我們只能找到一個對"sss111"的表示:
          000000abh: 08 00 11 01 00 06 73 73 73 31 31 31             ; ......sss111


          在程序執行的時候,Constant Pool會儲存在Method Area,而不是heap中.

          另外,對于""內容為空的字符串常量,會創建一個長度為0,內容為空的字符串放到Constant Pool中,
          而且Constant Pool在運行期是可以動態擴展的.


          關于String類的說明

          1.String使用private final char value[]來實現字符串的存儲,也就是說String對象創建之后,就不能
          再修改此對象中存儲的字符串內容,就是因為如此,才說String類型是不可變的(immutable).

          2.String類有一個特殊的創建方法,就是使用""雙引號來創建.例如new String("i am")實際創建了2個
          String對象,一個是"i am"通過""雙引號創建的,另一個是通過new創建的.只不過他們創建的時期不同,
          一個是編譯期,一個是運行期!

          3.java對String類型重載了+操作符,可以直接使用+對兩個字符串進行連接.

          4.運行期調用String類的intern()方法可以向String Pool中動態添加對象.

          String的創建方法一般有如下幾種
          1.直接使用""引號創建.
          2.使用new String()創建.
          3.使用new String("someString")創建以及其他的一些重載構造函數創建.
          4.使用重載的字符串連接操作符+創建.

          例1

           

              /*
              * "sss111"是編譯期常量,編譯時已經能確定它的值,在編譯
              * 好的class文件中它已經在String Pool中了,此語句會在
              * String Pool中查找等于"sss111"的字符串(用equals(Object)方法確定),
              * 如果存在就把引用返回,付值給s1.不存在就會創建一個"sss111"放在
              * String Pool中,然后把引用返回,付值給s1.
              * 
              
          */

              String s1 
          = "sss111"

              
          //此語句同上
              String s2 = "sss111";

              
          /*
              * 由于String Pool只會維護一個值相同的String對象
              * 上面2句得到的引用是String Pool中同一個對象,所以
              * 他們引用相等
              
          */

              System.out.println(s1 
          == s2); //結果為true

           


          例2
             

           /*
              * 在java中,使用new關鍵字會創建一個新對象,在本例中,不管在
              * String Pool中是否已經有值相同的對象,都會創建了一個新的
              * String對象存儲在heap中,然后把引用返回賦給s1.
              * 本例中使用了String的public String(String original)構造函數.
              
          */

              String s1 
          = new String("sss111"); 
              
              
          /*
               * 此句會按照例1中所述在String Pool中查找
               
          */

              String s2 
          = "sss111";
              
              
          /*
               * 由于s1是new出的新對象,存儲在heap中,s2指向的對象
               * 存儲在String Pool中,他們肯定不是同一個對象,只是
               * 存儲的字符串值相同,所以返回false.
               
          */

              System.out.println(s1 
          == s2); //結果為false


          例3
             

          String s1 = new String("sss111"); 
              
          /*
              * 當調用intern方法時,如果String Pool中已經包含一個等于此String對象
              * 的字符串(用 equals(Object)方法確定),則返回池中的字符串.否則,將此
              * String對象添加到池中,并返回此String對象在String Pool中的引用.
              
          */

              s1 
          = s1.intern();
              
              String s2 
          = "sss111";
              
              
          /*
               * 由于執行了s1 = s1.intern(),會使s1指向String Pool中值為"sss111"
               * 的字符串對象,s2也指向了同樣的對象,所以結果為true
               
          */

              System.out.println(s1 
          == s2);


          例4
             

              String s1 = new String("111"); 
              String s2 
          = "sss111";
              
              
          /*
              * 由于進行連接的2個字符串都是常量,編譯期就能確定連接后的值了,
              * 編譯器會進行優化直接把他們表示成"sss111"存儲到String Pool中,
              * 由于上邊的s2="sss111"已經在String Pool中加入了"sss111",
              * 此句會把s3指向和s2相同的對象,所以他們引用相同.此時仍然會創建出
              * "sss"和"111"兩個常量,存儲到String Pool中.

              
          */

              String s3 
          = "sss" + "111";
              
              
          /*
               * 由于s1是個變量,在編譯期不能確定它的值是多少,所以
               * 會在執行的時候創建一個新的String對象存儲到heap中,
               * 然后賦值給s4.
               
          */

              String s4 
          = "sss" + s1;
              
              System.out.println(s2 
          == s3); //true
              System.out.println(s2 == s4); //false
              System.out.println(s2 == s4.intern()); //true


          例5
          這個是The Java Language Specification中3.10.5節的例子,有了上面的說明,這個應該不難理解了

           

              package testPackage;
              
          class Test {
                      
          public static void main(String[] args) {
                              String hello 
          = "Hello", lo = "lo";
                              System.out.print((hello 
          == "Hello"+ " ");
                              System.out.print((Other.hello 
          == hello) + " ");
                              System.out.print((other.Other.hello 
          == hello) + " ");
                              System.out.print((hello 
          == ("Hel"+"lo")) + " ");
                              System.out.print((hello 
          == ("Hel"+lo)) + " ");
                              System.out.println(hello 
          == ("Hel"+lo).intern());
                      }

              }

              
          class Other static String hello = "Hello"; }

              
          package other;
              
          public class Other static String hello = "Hello"; }

           

          輸出結果為true true true true false true,請自行分析!


          結果上面分析,總結如下:
          1.單獨使用""引號創建的字符串都是常量,編譯期就已經確定存儲到String Pool中.
          2.使用new String("")創建的對象會存儲到heap中,是運行期新創建的.
          3.使用只包含常量的字符串連接符如"aa" + "aa"創建的也是常量,編譯期就能確定,已經確定存儲到String Pool中.
          4.使用包含變量的字符串連接符如"aa" + s1創建的對象是運行期才創建的,存儲在heap中.
          6.使用"aa" + s1以及new String("aa" + s1)形式創建的對象是否加入到String Pool中我不太確定,可能是必須
          調用intern()方法才會加入,希望高手能回答 @_@


          還有幾個經常考的面試題:

          1.

          String s1 = new String("s1") ;
          String s2 
          = new String("s1") ;


          上面創建了幾個String對象?
          答案:3個 ,編譯期Constant Pool中創建1個,運行期heap中創建2個.


          2.

          String s1 = "s1";
          String s2 
          = s1;
          s2 
          = "s2";


          s1指向的對象中的字符串是什么?
          答案: "s1"


          轉自 http://topic.csdn.net/u/20080929/02/4e0ef626-98ee-4d6d-96ed-fe40afe8290b.html

          posted on 2009-03-28 09:18 子非魚 閱讀(338) 評論(0)  編輯  收藏 所屬分類: JAVA
          主站蜘蛛池模板: 阳原县| 海林市| 兰西县| 衡山县| 扬中市| 六盘水市| 元氏县| 梧州市| 中宁县| 宁海县| 西平县| 崇仁县| 巴东县| 尼玛县| 佛坪县| 西乌珠穆沁旗| 泸水县| 镇雄县| 定结县| 武冈市| 潢川县| 肇庆市| 衡阳县| 井陉县| 刚察县| 芦山县| 灵宝市| 卓尼县| 醴陵市| 明星| 垦利县| 探索| 广东省| 长阳| 宁都县| 新宁县| 江孜县| 鹿泉市| 锡林浩特市| 遂平县| 忻城县|