最初听说的String、StringBuffer和StringBuilder三者之间的区别主要是下面这个版本:
String:字符串常量,字符串长度不可变。Java中String是immutable(不可变)的。用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改。
StringBuffer:字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用StringBuffer,如果想转成String类型,可以调用StringBuffer的toString()方法。
StringBuilder:字符串变量(非线程安全)。在内部,StringBuilder对象被当作是一个包含字符序列的变长数组。
一般情况下,速度从快到慢:StringBuilder > StringBuffer > String。
下面通过不同的角度来对这三个String相关的类型进行详细的分析和学习,主要通过源码以及反编译的字节码进行学习,另外对于常拿来比较三者之间性能的例子就不再重复了,整理下面内容的主要目标是深入理解这三者的区别。
下面将分别对这三者进行说明,都先从源码中观测一下其创建的过程,以及如何进行添加操作,随后对三个类型做同一种测试,即字符串的拼接,通过虚指令观测其虚拟机层面的执行原理,最后做一个总结。
String
下面是String在jdk中的源码片段,可以看出,String类中实际存放数据是一个以final 类型的char数组,也就是说该数组是不可变的。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; public String() { this.value = new char[0]; } public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } public String(String original) { this.value = original.value; this.hash = original.hash; } }
简单代码
public class StringTest { public static void main(String[] args) { String s = "string "; s += "is a good boy!"; s += "really?"; } }
从下面的虚指令中可以看出,在往字符串s中添加新内容的时候,其过程在下面代码中注释。
$ javap -p -v StringTest Classfile /home/hadoop/zhdd/StringTest.class Last modified Dec 6, 2015; size 511 bytes MD5 checksum f960070f295b4e22de6bffe8f06634f4 Compiled from "StringTest.java" public class StringTest SourceFile: "StringTest.java" minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #10.#19 // java/lang/Object."<init>":()V #2 = String #20 // string #3 = Class #21 // java/lang/StringBuilder #4 = Methodref #3.#19 // java/lang/StringBuilder."<init>":()V #5 = Methodref #3.#22 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #6 = String #23 // is a good boy! #7 = Methodref #3.#24 // java/lang/StringBuilder.toString:()Ljava/lang/String; #8 = String #25 // really? #9 = Class #26 // StringTest #10 = Class #27 // java/lang/Object ...... { ...... public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: ldc #2 // 将常量池中的"string "推到栈顶 2: astore_1 // 再将栈顶的"string "存入局部变量表 3: new #3 // new一个StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: aload_1 // 将一个局部变量加载到操作栈,即"string" 11: invokevirtual #5 // Method java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 14: ldc #6 // String is a good boy! 16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_1 23: new #3 // new一个StringBuilder,在这里可以看出来,每次拼接一个数组就要new一个StringBuilder 26: dup 27: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 30: aload_1 31: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 34: ldc #8 // String really? 36: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 39: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 42: astore_1 43: return LineNumberTable: line 8: 0 line 9: 3 line 10: 23 line 13: 43 }
StringBuilder
通过StringBuilder可以看出,StringBuilder继承自AbstractStringBuilder,而且初始化以及appen新的字符串的主要操作都在AbstractStringBuilder中,因此下面主要看一下AbstractStringBuilder的源码。
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence { public StringBuilder() { super(16); } public StringBuilder(int capacity) { super(capacity); } public StringBuilder append(String str) { super.append(str); return this; } }
下面是AbstractStringBuilder的部分源码,在源码中可以看出,AbstractStringBuilder在存放数值的也是一个char型的数组,不同的是,没有加final修饰符。
初始化的过程和String类似,在append的时候可以看出,AbstractStringBuilder是类似于扩充数组大小的方式先扩容,再添加进去新的元素。
abstract class AbstractStringBuilder implements Appendable, CharSequence { char[] value; int count; AbstractStringBuilder(int capacity) { value = new char[capacity]; } public AbstractStringBuilder append(String str) { if (str == null) str = "null"; int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } }
下面是StringBuilder拼接字符串的一个简单的例子。
public class StringBuilderTest { public static void main(String[] args) { StringBuilder sd = new StringBuilder(); sd.append("Stringbuilder "); sd.append("is a good boy!"); } }
在虚指令中可以看出,StringBuilder和String不同的是,StringBuilder在append字符串的时候直接拼接就行,不需要每次都new一个新的StringBuilder对象。
$ javap -p -v StringBuilderTest ...... Constant pool: #1 = Methodref #8.#17 // java/lang/Object."<init>":()V #2 = Class #18 // java/lang/StringBuilder #3 = Methodref #2.#17 // java/lang/StringBuilder."<init>":()V #4 = String #19 // Stringbuilder #5 = Methodref #2.#20 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #6 = String #21 // is a good boy! #7 = Class #22 // StringBuilderTest ...... { public StringBuilderTest(); 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 1: 0 public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #2 // class java/lang/StringBuilder 3: dup 4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V 7: astore_1 8: aload_1 9: ldc #4 // String Stringbuilder 11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: pop 15: aload_1 16: ldc #6 // String is a good boy! 18: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: pop 22: return ...... }
StringBuffer
可以看出StringBuffer也是继承自AbstractStringBuilder,而且它的主要操作都是调用super()来操作实现的,唯一不同的是在append等操作的时候添加了synchronized限定,因此是线程安全的。由于StringBuffer和StringBuilder的主要操作都是在父类AbstractStringBuilder中完成的,因此所谓的StringBuilder比StringBuffer的速度快的主要原因应该是synchronized造成的。
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence { public StringBuffer() { super(16); } public StringBuffer(String str) { super(str.length() + 16); append(str); } public synchronized StringBuffer append(String str) { super.append(str); return this; } public synchronized StringBuffer append(StringBuffer sb) { super.append(sb); return this; } }
如下示例基本和StringBuilder的代码相同。
public class StringBufferTest { public static void main(String[] args) { StringBuffer sb = new StringBuffer(); sb.append("string buffer:"); sb.append("is a good boy!"); } }
在虚指令中看,可以看出,两者的操作基本没有太多区别,不过多解释。
$ javap -v -p StringBufferTest ...... Constant pool: #1 = Methodref #8.#17 // java/lang/Object."<init>":()V #2 = Class #18 // java/lang/StringBuffer #3 = Methodref #2.#17 // java/lang/StringBuffer."<init>":()V #4 = String #19 // string buffer: #5 = Methodref #2.#20 // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; #6 = String #21 // is a good boy! #7 = Class #22 // StringBufferTest #8 = Class #23 // java/lang/Object #9 = Utf8 <init> ...... { public StringBufferTest(); 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 1: 0 public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #2 // class java/lang/StringBuffer 3: dup 4: invokespecial #3 // Method java/lang/StringBuffer."<init>":()V 7: astore_1 8: aload_1 9: ldc #4 // String string buffer: 11: invokevirtual #5 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 14: pop 15: aload_1 16: ldc #6 // String is a good boy! 18: invokevirtual #5 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 21: pop 22: return LineNumberTable: line 3: 0 line 4: 8 line 5: 15 line 6: 22 }
总结
通过上面的测试结果可以解释String、StringBuffer和StringBuilder之间的区别。
速度比较:StringBuilder > StringBuffer > String。
为什么有这样的情况,首先分析StringBuilder > String,这个的主要原因可以在两个例子对比中看出,在String中,每次拼接新的字符串,都会new一个StringBuilder对象,也就是说如果拼接N次,就需要new出来N个StringBuilder对象,这样无疑上速度会慢很多。
再分析StringBuilder > StringBuffer的原因,这个其实已经比较明确,在前文中指出,StringBuffer和StringBuilder的主要不同是StringBuffer加了synchronized修饰,其余的操作都是继承自AbstractStringBuilder父类。
线程安全就是synchronized的却别,在源码中可以看到。
之前理解的时候一直有一个误区,就是在性能的区分上,StringBuilder比String快的原因是StringBuilder没有存放在常量池中而是存放在一些特殊的区域,但是在以上的例子中可以看出,其实在拼接过程中的所有的string都是存放在常量池中的,不同的主要是拼接的方式。