字符串拼接原理以及字符串常見面試題


第一種情況

    /*
        * 第一種情況
        * 證明:是否在編譯的時候完成拼接
        * */
        String str = "a" + "b";

常量池信息:

查看常量池信息必須通過 javap -v 命令來查看Class文件(java文件編譯后的文件)

Constant pool:
   #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #2 = String             #21            // ab
   #3 = Class              #22            // com/test/StringTest2
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               LocalVariableTable
  #10 = Utf8               this
  #11 = Utf8               Lcom/test/StringTest2;
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               args
  #15 = Utf8               [Ljava/lang/String;
  #16 = Utf8               str
  #17 = Utf8               Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               StringTest2.java
  #20 = NameAndType        #5:#6          // "<init>":()V
  #21 = Utf8               ab
  #22 = Utf8               com/test/StringTest2
  #23 = Utf8               java/lang/Object

可以看到 #21 是字符串"ab"且沒有單獨的 "a" 和 "b",說明String str = "a" + "b" 在編譯時期就已經拼接在一塊了,效果與 String str = "ab" 相同。

進一步說明,第一種情況只在字符串常量池中創建了一個"ab"對象,沒有"a"對象和"b"對象。(類加載過程中Class文件的常量池內容會進入字符串常量池)。

第二種情況

      /*
        * 第二種情況
        * 證明:是否是在編譯的時候完成拼接,如果不是,那么是按照什么方式進行的拼接。
        * */
        String str = "a";
        String str1 = "b";
        String str3 = str + str1;

 

常量池信息

Constant pool:
   #1 = Methodref          #9.#27         // java/lang/Object."<init>":()V
   #2 = String             #28            // a
   #3 = String             #29            // b
   #4 = Class              #30            // java/lang/StringBuilder
   #5 = Methodref          #4.#27         // java/lang/StringBuilder."<init>":()V
   #6 = Methodref          #4.#31         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #7 = Methodref          #4.#32         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #8 = Class              #33            // com/test/StringTest2
   #9 = Class              #34            // java/lang/Object
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               LocalVariableTable
  #15 = Utf8               this
  #16 = Utf8               Lcom/test/StringTest2;
  #17 = Utf8               main
  #18 = Utf8               ([Ljava/lang/String;)V
  #19 = Utf8               args
  #20 = Utf8               [Ljava/lang/String;
  #21 = Utf8               str
  #22 = Utf8               Ljava/lang/String;
  #23 = Utf8               str1
  #24 = Utf8               str3
  #25 = Utf8               SourceFile
  #26 = Utf8               StringTest2.java
  #27 = NameAndType        #10:#11        // "<init>":()V
  #28 = Utf8               a
  #29 = Utf8               b
  #30 = Utf8               java/lang/StringBuilder
  #31 = NameAndType        #35:#36        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #32 = NameAndType        #37:#38        // toString:()Ljava/lang/String;
  #33 = Utf8               com/test/StringTest2
  #34 = Utf8               java/lang/Object
  #35 = Utf8               append
  #36 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #37 = Utf8               toString
  #38 = Utf8               ()Ljava/lang/String;

 

從常量池中我們可以看到 #28 和 #29 分別為字符串 "a" 和 "b",並且不存在字符串 "ab",所以可以排除第二種情況是在編譯期完成的拼接。

那么不是編譯器完成拼接那么是通過什么方式進行拼接的呢?我們可以通過查看反匯編指令,來進一步了解拼接的執行過程。

反匯編指令可以通過 javap -v 或者 java -c 查看Class文件(java編譯后的文件)得到。

stack=2, locals=4, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: new           #4                  // class java/lang/StringBuilder
         9: dup
        10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
        13: aload_1
        14: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        17: aload_2
        18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        24: astore_3
        25: return

 

反匯編指令詳解可以百度,這里不做過多解釋

我們可以看到 第6行new了一個StringBuilder對象,之后執行了兩次append()方法,把字符串 "a" 和 "b" 拼接到一塊,然后通過StringBuilder.toString()方法返回一個String對象,該String 對象就是完成拼接后的對象。

這也意味着,通過這種方式將來在類加載的過程字符串常量池中只會保存 "a" 和 "b" 這兩個對象,而沒有保存 "ab" 這個對象。

當然從編譯角度看,編譯時期無法識別變量的具體值的,所以 "ab" 這個字符串自然也不會保存到Class文件的常量池中。

這里再提一點:StringBuilder.toString()方法返回的是一個拼接后的String對象,如果字符串很長的時候,盡量不要多次使用StringBuilder.toString()方法,否則會浪費空間甚至造成內存溢出。 

第三種情況

    /*
        * 第三種情況
        * 與第二種差不多
        * */
        String str = new String("a") + new String("b");

 

常量池:

Constant pool:
   #1 = Methodref          #11.#27        // java/lang/Object."<init>":()V
   #2 = Class              #28            // java/lang/StringBuilder
   #3 = Methodref          #2.#27         // java/lang/StringBuilder."<init>":()V
   #4 = Class              #29            // java/lang/String
   #5 = String             #30            // a
   #6 = Methodref          #4.#31         // java/lang/String."<init>":(Ljava/lang/String;)V
   #7 = Methodref          #2.#32         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = String             #33            // b
   #9 = Methodref          #2.#34         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #10 = Class              #35            // com/test/StringTest2
  #11 = Class              #36            // java/lang/Object
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lcom/test/StringTest2;
  #19 = Utf8               main
  #20 = Utf8               ([Ljava/lang/String;)V
  #21 = Utf8               args
  #22 = Utf8               [Ljava/lang/String;
  #23 = Utf8               str
  #24 = Utf8               Ljava/lang/String;
  #25 = Utf8               SourceFile
  #26 = Utf8               StringTest2.java
  #27 = NameAndType        #12:#13        // "<init>":()V
  #28 = Utf8               java/lang/StringBuilder
  #29 = Utf8               java/lang/String
  #30 = Utf8               a
  #31 = NameAndType        #12:#37        // "<init>":(Ljava/lang/String;)V
  #32 = NameAndType        #38:#39        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #33 = Utf8               b
  #34 = NameAndType        #40:#41        // toString:()Ljava/lang/String;
  #35 = Utf8               com/test/StringTest2
  #36 = Utf8               java/lang/Object
  #37 = Utf8               (Ljava/lang/String;)V
  #38 = Utf8               append
  #39 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #40 = Utf8               toString
  #41 = Utf8               ()Ljava/lang/String;

 

反匯編

   Code:
      stack=4, locals=2, args_size=1
         0: new           #2                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
         7: new           #4                  // class java/lang/String
        10: dup
        11: ldc           #5                  // String a
        13: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: new           #4                  // class java/lang/String
        22: dup
        23: ldc           #8                  // String b
        25: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        31: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        34: astore_1
        35: return

第三種情況和第二種情況不同點:第三種情況會在堆里面多創建兩個對象,其它的都一樣

———————————————————————————————————————————————————————————————————————————————————————————————————

下面再介紹一下String類型的intern()方法,在jdk1.8中 intern()方法會先去判斷字符串常量池中是否存在想要查找的字符串對象,如果存在則返回字符串常量池中對象的地址;如果不存在則返回當前對象(誰調用這個方法,誰就是當前對象)的引用,並且在字符串常量池中創建指向當前對象的引用。注:jdk 1.6與jdk1.8有所不同,有興趣的可以了解下jdk1.6的intern()方法。

舉個例子:

        String str = new String("ab");
        System.out.println(str.intern()==str);    //false
        String str1 = new String("c") + new String("d");
        System.out.println(str1.intern()==str1);  //true    

 

第一種情況,會同時在字符串常量池和堆空間中各生成一個對象,所以str.intern返回的是字符串常量池中 "ab" 的地址,str返回的是堆空間中 "ab" 的地址。

第二種情況,只會在堆空間中生成一個對象,所以str1.intern()返回的是str1對象即堆空間對象的地址,並且會在字符串常量池中創建一個指向堆空間對象的引用。

那么也就是說,如果創建一個引用指向 "cd",比如 String str2 = "cd",那么str1.intern()==str2 也是成立的,但第一種情況就不成立。

 

 

面試題:

        String str = new String("ab");
        String str1 = new String("ab");
        String str2 = "ab";
        String str3 = "ab";
        System.out.println(str==str1);  //false
        System.out.println(str2==str3);  //true
        System.out.println(str2==str);  //false
        System.out.println(str.intern()==str2); //true
        System.out.println(str.intern()==str1.intern());  //true    
        String str = "a";
        String str1 = "b";
        String str2 = str + str1;
        String str3 = "ab";
        String str4 = str2.intern(); //str4在str3之前定義,則結果為兩個true.
        System.out.println(str2 == str3);//flase
        System.out.println(str2 == str4);//false
     //問:創建幾個對象
        //答:最明顯的字符串常量池分別創建了 “a”和"b",堆空間創建了 "a"和"b",然后字符串拼接會創建StringBuilder對象,
        // StringBuilder對象會調用toString()方法產生拼接后的String對象,所以總共創建了6個對象
        String str = new String("a") + new String("b");

 

 

如果有大佬發現不正確的地方,歡迎指正,我會第一時間修改。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM