String:常量,不可变,不适合用来字符串拼接,每次都是新创建的对象,消耗较大。
StringBuffer:适合用来作字符串拼接
StringBuilder:JDK1.5引入,适合用来作字符串拼接,与StringBuffer区别是他不是线程安全的
接下来进入正题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区(汇编指令)、局部变量表和代码行偏移映射三个部分。
javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。
当然这些信息中,有些信息(如本地变量表、指令和代码行偏移量映射表、常量池中方法的参数名称等等)需要在使用javac编译成class文件时,指定参数才能输出,比如,你直接javac xx.java,就不会在生成对应的局部变量表等信息,如果你使用javac -g xx.java就可以生成所有相关信息了。
通过反编译生成的汇编代码,我们可以深入的了解java代码的工作机制。比如我们可以查看i++;这行代码实际运行时是先获取变量i的值,然后将这个值加1,最后再将加1后的值赋值给变量i。
通过局部变量表,我们可以查看局部变量的作用域范围、所在槽位等信息,甚至可以看到槽位复用等信息。