导读
- String、StringBuffer、StringBuilder 的区别以及如何正确使用
- 如何理解 String 不可变?
- String、StringBuffer、StringBuilder 在 JVM 内存中的分布,以及面试常问:Java 不同字符类会创建几个对象?
版本及环境
String、StringBuffer、StringBuilder
- String 是不可变字符串,StringBuffer、StringBuilder 是可变字符串。
- StringBuffer 是线程安全的,StringBuilder 是线程非安全的。StringBuilder 具有 StringBuffer 一切能力,当不涉及多线程安全时,优先使用 StringBuilder,没有同步操作,性能更好。
如何理解 String 不可变
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
....
}
1、value[] 数组是 final 类型,表示 value 的引用地址不可变,但是 Arrary 数组是可变的
final char[] charArray = {'a', 'b', 'c', 'd', 'e'};
char ch = 'z';
charArray[0] = ch;
System.out.println(charArray);
2、但是由于 value[] 数组私有,且没有提供外部访问能力,因此无法修改
String 不可变的好处
String HashCode缓存
- String的HashCode在比如HashMap等容器当中都有使用,String的不变性保证了HashCode的不变性,不必每次去计算新的HashCode,这也是Map喜欢将String作为Key的原因,这是一种性能考虑。
安全性
- String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患,比如使用的url被修改。
可变 StringBuilder vs StringBuffer 与线程安全
- StringBuilder、StringBuffer 都是可变的,StringBuffer 是线程安全的,StringBuilder 是线程非安全的。StringBuilder 具有 StringBuffer 一切能力,当不涉及多线程安全是,优先使用 StringBuilder,没有同步操作,性能更好。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
字符常量池
- Java 内部维护了一个字符常量池,当创建一个字符 String 时,如果已经在字符常量池中存在,则使用该存在该变量,而不会创建新的。

思考一下
- 下面的代码你可以说出它们的创建过程,以及在 JVM 内存中的分配?
String str1 = "abc";
String str2 = new String("abc");
String str3 = "abc" + "def";
String str4 = "abc" + new String("def");
String str41 = new String("def") + "abc";
String str5 = new String("abc") + new String("def");
String str6 = "defabc";
""、new String()、new StringBuffer()、new StringBuilder() 与字符串常量池
- 从上面可以看出,""、new String()、StringBuffer()、StringBuilder() 创建字符串的过程和结果并不一样,我们分为以下几种情况讨论:
"" 和 new String()
String str1 = "abc";
String str2 = "abc";
String str2 = new String("abc");

StringBuffer() 和 StringBuilder()
- 两者都是在堆中创建对象,并不涉及字符串常量池,char 数组维护在堆的对象中。
拓展: 当两个字符串连接时,JVM 底层是如何实现的
String str3 = "abc" + "def";
String str4 = "abc" + new String("def");
String str41 = new String("def") + "abc";
String str5 = new String("abc") + new String("def");
String str6 = "defabc";

字节码分析
完整源码
public class MainTest {
public static void main(String[] args) throws InterruptedException {
String str1 = "abc";
String str2 = new String("abc");
String str3 = "abc" + "def";
String str4 = "abc" + new String("def");
String str41 = new String("def") + "abc";
String str5 = new String("abc") + new String("def");
String str6 = "defabc";
}
}
字节码
// javap -c .\MainTest.class
Compiled from "MainTest.java"
public class org.example.MainTest {
public org.example.MainTest();
Code:
0: aload_0
1: invokespecial
4: return
public static void main(java.lang.String[]) throws java.lang.InterruptedException;
Code:
---------------------------------------------String str1 = "abc"; --------------------------------------------------------------------------
0: ldc
2: astore_1
---------------------------------------------String str2 = new String("abc");---------------------------------------------------------------
3: new
6: dup
7: ldc
9: invokespecial
12: astore_2
---------------------------------------------String str3 = "abc" + "def";-------------------------------------------------------------------
13: ldc
15: astore_3
---------------------------------------------String str4 = "abc" + new String("def");-------------------------------------------------------
16: new
19: dup
20: invokespecial
23: ldc
25: invokevirtual
28: new
31: dup
32: ldc
34: invokespecial
37: invokevirtual
40: invokevirtual
43: astore 4
--------------------------------------------------------------------------------------------------------------------------------------------
45: new
48: dup
49: invokespecial
52: new
55: dup
56: ldc
58: invokespecial
61: invokevirtual
64: ldc
66: invokevirtual
69: invokevirtual
72: astore 5
74: new
77: dup
78: invokespecial
81: new
84: dup
85: ldc
87: invokespecial
90: invokevirtual
93: new
96: dup
97: ldc
99: invokespecial
102: invokevirtual
105: invokevirtual
108: astore 6
110: ldc
112: astore 7
114: return
}
总结
- 当用于 url、路径地址 等需要保证不可以变的使用 String,可变的场景下需要保证线程安全使用 StringBuffer,不需要保证线程安全使用 StringBuilder,减少同步加锁开销。
- 常见面试题:不同使用字符串的方式会创建多少个对象:
案例一:
String str3 = "abc" + "def";
编译阶段优化为 “abcdef” 在常量池创建一个对象 “abcdef”
案例二:
String str4 = "abc" + new String("def")
"abc" 在常量池创建一个对象
new String("def") 在堆中创建一个 String 对象,常量池创建对象 "def"
创建一个 StringBuilder 对象用于拼接两个字符串,最后调用 toString() 方法创建一个 String 对象