String和StringBuilder和StringBuffer
一、基本概念
类名 | 线程安全 | 是否可变 | 性能 | 适用场景 |
---|---|---|---|---|
String | 是 | 否 | 最差 | 字符串内容固定、少量拼接 |
StringBuilder | 否 | 是 | 最优 | 单线程场景下大量字符串拼接 |
StringBuffer | 是 | 是 | 较差 | 多线程场景下大量字符串拼接 |
// 单线程拼接测试
long start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 9999; i++) {sb.append("a");
}
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
9999次拼接操作的直接比较:
-
String
:大约1116.5毫秒,内存使用约31.4 MB。 -
StringBuilder
:大约1.3毫秒,内存使用约1 MB。 -
StringBuffer
:大约2.9毫秒,内存使用约1 MB。
二、源码分析与底层原理
1. String 不可变性
String
是 Java 中最常用的类之一,被设计为**不可变(immutable)**对象,意味着一旦创建,其内容就不可更改。
不可变性的目的:
-
提高安全性(如在网络地址、文件路径、ClassName 等场景中不可被修改)
-
实现 hash 缓存,提升效率(
hashCode
可缓存) -
支持常量池(共享、节省内存)
-
多线程环境下天然线程安全
关键词: 安全(并发),效率,内存
JDK 8 实现:char[]
结构
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {private final char value[]; // 核心字段,字符数组,不可变private int hash; // hashCode 缓存public String(String original) {this.value = original.value;this.hash = original.hash;}
}
-
类是
final
,不能被继承,防止子类破坏其特性 -
value
数组是final
,确保引用地址不可变; -
char[]
是私有的,外部无法直接修改; -
所有修改操作(如
concat
,replace
,substring
)都会返回新对象,而不是改变原对象。
JDK 9+ 实现:byte[] + coder
结构(Compact Strings)
为了节省内存,从 JDK 9 开始,String
改为内部使用 byte[]
存储,同时通过 coder
字段标识编码方式(Latin-1 或 UTF-16):
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {private final byte[] value; // 替代 char[]private final byte coder; // Latin1(0) 或 UTF16(1)// 源码中的注释:// The value is used for character storage.// The coder encodes whether it's Latin-1 or UTF-16.public String(String original) {this.value = original.value;this.coder = original.coder;}
}
优点:空间节省
-
大部分英文字符只需 1 字节(Latin-1 编码),一个
char
占用 2 字节,大幅减少内存使用。 -
UTF-16 编码用于中文等多字节字符,自动判断切换。
-
JVM 内部使用
StringLatin1
和StringUTF16
工具类进行高效的操作。
2. String 拼接机制
Java 中的 +
拼接语法底层会使用 StringBuilder
(在编译期优化)。
示例:
String a = "hello";
String b = a + "world";
编译后等价于:
String b = new StringBuilder().append(a).append("world").toString();
这是 Java 编译器对常量拼接的一种优化手段,避免频繁创建 String
对象。
三、intern() 方法机制
1. 方法定义
intern()
方法用于手动将字符串加入字符串常量池(StringTable)。
public native String intern();
2. intern() 行为差异
版本 | 常量池位置 | 行为特点 |
---|---|---|
JDK 6 | 方法区(永久代) | 第一次调用 intern() 会复制一份字符串对象到常量池 |
JDK 7+ | 堆中 | 不再复制,而是将堆中首次出现的字符串引用放入常量池(节省内存) |
3. 示例对比
String s1 = new StringBuilder("aa").append("aa").toString();
System.out.println(s1.intern() == s1); // JDK6: false, JDK7+: true(首次出现)
解释:
-
JDK6:
intern()
将 “aaaa” 的副本放入常量池,s1
指向堆,不相等。 -
JDK7+:直接将堆中的 s1 引用放入常量池,二者相等。
如果把“aaaa”改为"java"呢?
四、StringBuilder 与 StringBuffer 实现机制
1. 共同点
-
二者均基于可扩容的
char[]
实现字符存储,并支持动态拼接。 -
扩容为
newCapacity
=(oldCapacity + 1) * 2
。 -
初始容量为 16。
抽象父类:
abstract class AbstractStringBuilder {char[] value;int count;public AbstractStringBuilder append(String str) {// 省略扩容逻辑System.arraycopy(...); // 本地方法}
}
2. StringBuilder
线程不安全,高性能,推荐用于单线程环境。
public final class StringBuilder extends AbstractStringBuilder {public StringBuilder() {super(16);}
}
3. StringBuffer
线程安全,在方法上使用 synchronized
修饰。
public final class StringBuffer extends AbstractStringBuilder implements Serializable {@Overridepublic synchronized StringBuffer append(String str) {super.append(str);return this;}
}
选择指南:
graph TDA[需要修改字符串?] -->|No| B[使用String]A -->|Yes| C{涉及多线程?}C -->|No| D[使用StringBuilder]C -->|Yes| E[使用StringBuffer]