String、StringBuffer、StringBuilder都是JAVA中常用的字符串操作类,对于他们的区别大家也都能耳熟能详,但底层到底是怎样实现的呢?今天就再深入分析下这三种字符串操作的区别、各自的原理及使用场景。
请尊重作者劳动成果,转载请标明原文链接:
https://www.cnblogs.com/jpcflyer/p/9280501.html
一、String
先来看一下JDK中String中的部分源码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash; // Default to 0
public String() {
this.value = new char[0];
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
...
}
可以看到String类、以及value都是final类型的,这样就表明String是无法被继承的,value是无法被改写的。当通过String的构造函数初始化新的String对象时,也只是根据传入的引用对象的value和hashcode进行了赋值。看下面的例子:
public class StringTest {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
String Str3 = new String("abc");
}
}
执行javac StringTest.java后,通过javap -v StringTest.class看下生成的class文件:
Classfile /C:/Users/jiang/workspace/test/src/test/StringTest.class
Last modified 2018-7-8; size 363 bytes
MD5 checksum f7e4243b0247fb20c5a336d4ba0a580f
Compiled from "StringTest.java"
public class test.StringTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = String #16 // abc
#3 = Class #17 // java/lang/String
#4 = Methodref #3.#18 // java/lang/String."<init>":(Ljava/lang/String;)V
#5 = Class #19 // test/StringTest
#6 = Class #20 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 StringTest.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Utf8 abc
#17 = Utf8 java/lang/String
#18 = NameAndType #7:#21 // "<init>":(Ljava/lang/String;)V
#19 = Utf8 test/StringTest
#20 = Utf8 java/lang/Object
#21 = Utf8 (Ljava/lang/String;)V
{
public test.StringTest();
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 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=4, args_size=1
0: ldc #2 // String abc
2: astore_1
3: ldc #2 // String abc
5: astore_2
6: new #3 // class java/lang/String
9: dup
10: ldc #2 // String abc
12: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: return
LineNumberTable:
line 6: 0
line 7: 3
line 8: 6
line 9: 16
}
SourceFile: "StringTest.java"
可以看到对于相同的字符串“abc”的引用都是相同的(对于常量池中的相同位置),这样能够节省内存空间,但是缺点就是对于频繁的字符串拼接操作,会造成内存空间的浪费。(需要注意的是这种字符串的拼接操作,从JDK8 开始,会自动被编译成StringBuilder,是不是很666^_^,但还是建议不通过JDK途径去自动转。)看下面的代码:
public class StringTest {
public static void main(String[] args) {
String str1 = "abc";
//String str2 = "abc";
//String str3 = new String("abc");
String str4 = str1 + "d";
String str5 = str4 + "e";
}
}
然后再通过javap看下class文件:
Classfile /C:/Users/jiang/workspace/test/src/test/StringTest.class
Last modified 2018-7-8; size 493 bytes
MD5 checksum c02bd18ed3ecbe46f9859bf5e272c663
Compiled from "StringTest.java"
public class test.StringTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #10.#19 // java/lang/Object."<init>":()V
#2 = String #20 // abc
#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 // d
#7 = Methodref #3.#24 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#8 = String #25 // e
#9 = Class #26 // test/StringTest
#10 = Class #27 // java/lang/Object
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 main
#16 = Utf8 ([Ljava/lang/String;)V
#17 = Utf8 SourceFile
#18 = Utf8 StringTest.java
#19 = NameAndType #11:#12 // "<init>":()V
#20 = Utf8 abc
#21 = Utf8 java/lang/StringBuilder
#22 = NameAndType #28:#29 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#23 = Utf8 d
#24 = NameAndType #30:#31 // toString:()Ljava/lang/String;
#25 = Utf8 e
#26 = Utf8 test/StringTest
#27 = Utf8 java/lang/Object
#28 = Utf8 append
#29 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#30 = Utf8 toString
#31 = Utf8 ()Ljava/lang/String;
{
public test.StringTest();
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 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: ldc #2 // String abc
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String d
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_2
23: new #3 // class java/lang/StringBuilder
26: dup
27: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
30: aload_2
31: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: ldc #8 // String e
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_3
43: return
LineNumberTable:
line 6: 0
line 9: 3
line 10: 23
line 11: 43
}
SourceFile: "StringTest.java"
二、StringBuilder
也是先来看StringBuilder的源码:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
public StringBuilder() {
super(16);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder append(String str) {
super.append(str);
return this;
}
...
}
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)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
...
}
可以看到StringBuilder的value是个char数组,(当然从JDK9开始,value从char数组变成了byte数组)。每次append时都是通过调用native的System.arraycopy实现的(在getChars中调用的)。
三、StringBuffer
S tringBuffer的源码如下:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
private transient char[] toStringCache;
public StringBuffer() {
super(16);
}
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
...
}
和StringBuilder一样,都是用了char数组保存value,append也是调用了AbstractStringBuilder的append方法。区别只是在于char数组加了transient关键字,以及方法上加了synchronized方法。
综上所述,String、StringBuilder、StringBuffer的使用场景如下:
当处理定长字符串时,建议用String;
当处理变长字符串时,并且是单线程环境时,建议用StringBuilder;
当处理变长字符串时,并且是多线程环境时,建议用StringBuffer。