字符串拼接原理以及字符串常见⾯试题
第⼀种情况
/*
* 第⼀种情况
* 证明:是否在编译的时候完成拼接
* */
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小时内删除。
发表评论