字符串拼接原理以及字符串常见⾯试题
第⼀种情况
    /*
* 第⼀种情况
* 证明:是否在编译的时候完成拼接
* */
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/String:()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/String:()Ljava/lang/String;
24: astore_3
25: return
反汇编指令详解可以百度,这⾥不做过多解释
我们可以看到第6⾏new了⼀个StringBuilder对象,之后执⾏了两次append()⽅法,把字符串 "a" 和 "b" 拼接到⼀块,然后通过String()⽅法返回⼀个String对象,
该String 对象就是完成拼接后的对象。
这也意味着,通过这种⽅式将来在类加载的过程字符串常量池中只会保存 "a" 和 "b" 这两个对象,⽽没有保存 "ab" 这个对象。
当然从编译⾓度看,编译时期⽆法识别变量的具体值的,所以 "ab" 这个字符串⾃然也不会保存到Class⽂件的常量池中。
这⾥再提⼀点:String()⽅法返回的是⼀个拼接后的String对象,如果字符串很长的时候,尽量不要多次使⽤String()⽅法,否则会浪费空间甚⾄造成
内存溢出。
第三种情况
    /*
* 第三种情况
* 与第⼆种差不多
* */
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/String:()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/String:()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");
长度介于0和59字符串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");
如果有⼤佬发现不正确的地⽅,欢迎指正,我会第⼀时间修改。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。