String:常量,不可變,不適合用來字符串拼接,每次都是新創(chuàng)建的對象,消耗較大。
StringBuffer:適合用來作字符串拼接
StringBuilder:JDK1.5引入,適合用來作字符串拼接,與StringBuffer區(qū)別是他不是線程安全的
接下來進入正題String”+”拼接底層實現原理
public class StringDemo01 {
public static void main(String[] args) {
String a = "abc";
String b = "def";
System.out.println("abcdef" == a+b);
}
}
通過javap命令分析java匯編指令可以得知底層使用了StringBuilder實現
javap -v StringDemo.class
Classfile StringDemo01.class
Last modified 2020-6-6; size 730 bytes
MD5 checksum 8847314e26430be9703f9490a6d8ecf3
Compiled from "StringDemo01.java"
public class string.StringDemo01
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #12.#25 // java/lang/Object."<init>":()V
#2 = String #26 // abc
#3 = String #27 // def
#4 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream;
#5 = String #30 // abcdef
#6 = Class #31 // java/lang/StringBuilder
#7 = Methodref #6.#25 // java/lang/StringBuilder."<init>":()V
#8 = Methodref #6.#32 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#9 = Methodref #6.#33 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#10 = Methodref #34.#35 // java/io/PrintStream.println:(Z)V
#11 = Class #36 // string/StringDemo01
#12 = Class #37 // java/lang/Object
#13 = Utf8 <init>
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 StackMapTable
#20 = Class #38 // "[Ljava/lang/String;"
#21 = Class #39 // java/lang/String
#22 = Class #40 // java/io/PrintStream
#23 = Utf8 SourceFile
#24 = Utf8 StringDemo01.java
#25 = NameAndType #13:#14 // "<init>":()V
#26 = Utf8 abc
#27 = Utf8 def
#28 = Class #41 // java/lang/System
#29 = NameAndType #42:#43 // out:Ljava/io/PrintStream;
#30 = Utf8 abcdef
#31 = Utf8 java/lang/StringBuilder
#32 = NameAndType #44:#45 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#33 = NameAndType #46:#47 // toString:()Ljava/lang/String;
#34 = Class #40 // java/io/PrintStream
#35 = NameAndType #48:#49 // println:(Z)V
#36 = Utf8 string/StringDemo01
#37 = Utf8 java/lang/Object
#38 = Utf8 [Ljava/lang/String;
#39 = Utf8 java/lang/String
#40 = Utf8 java/io/PrintStream
#41 = Utf8 java/lang/System
#42 = Utf8 out
#43 = Utf8 Ljava/io/PrintStream;
#44 = Utf8 append
#45 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#46 = Utf8 toString
#47 = Utf8 ()Ljava/lang/String;
#48 = Utf8 println
#49 = Utf8 (Z)V
{
public string.StringDemo01();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 11: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=1
0: ldc #2 // String abc
2: astore_1
3: ldc #3 // String def
5: astore_2
6: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #5 // String abcdef
11: new #6 // class java/lang/StringBuilder
14: dup
15: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
18: aload_1
19: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_2
23: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
26: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: if_acmpne 36
32: iconst_1
33: goto 37
36: iconst_0
37: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
40: return
LineNumberTable:
line 14: 0
line 15: 3
line 17: 6
line 21: 40
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 36
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
}
SourceFile: "StringDemo01.java"
String拼接,有字符串變量參與時,中間會產生StringBuilder對象(JDK1.5之前產生StringBuffer)
字符串拼接原理:運行時, 兩個字符串str1, str2的拼接首先會調用 String.valueOf(obj),這個Obj為str1,而String.valueOf(Obj)中的實現是return obj == null ? “null” : obj.toString(), 然后產生StringBuilder, 調用的StringBuilder(str1)構造方法, 把StringBuilder初始化,長度為str1.length()+16,并且調用append(str1)! 接下來調用StringBuilder.append(str2), 把第二個字符串拼接進去, 然后調用StringBuilder.toString返回結果!
StringBuilder(str) 底層調用
/**
* Constructs a string builder initialized to the contents of the
* specified string. The initial capacity of the string builder is
* {@code 16} plus the length of the string argument.
*
* @param str the initial contents of the buffer.
*/
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
StringBuilder.toString 底層調用
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
Javap
介紹其中code區(qū)(匯編指令)、局部變量表和代碼行偏移映射三個部分。
javap是jdk自帶的反解析工具。它的作用就是根據class字節(jié)碼文件,反解析出當前類對應的code區(qū)(匯編指令)、本地變量表、異常表和代碼行偏移量映射表、常量池等等信息。
當然這些信息中,有些信息(如本地變量表、指令和代碼行偏移量映射表、常量池中方法的參數名稱等等)需要在使用javac編譯成class文件時,指定參數才能輸出,比如,你直接javac xx.java,就不會在生成對應的局部變量表等信息,如果你使用javac -g xx.java就可以生成所有相關信息了。
通過反編譯生成的匯編代碼,我們可以深入的了解java代碼的工作機制。比如我們可以查看i++;這行代碼實際運行時是先獲取變量i的值,然后將這個值加1,最后再將加1后的值賦值給變量i。
通過局部變量表,我們可以查看局部變量的作用域范圍、所在槽位等信息,甚至可以看到槽位復用等信息。