一、核心区别对比表
| 维度 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可变性 | ❌ 不可变 | ✅ 可变 | ✅ 可变 |
| 线程安全 | ✅(天然安全) | ✅ 线程安全(方法synchronized) | ❌ 线程不安全 |
| 性能 | 最低(频繁修改时) | 中等 | 最高 |
| 底层存储 | final char[] |
char[](可变) |
char[](可变) |
| 内存开销 | 最高(产生大量中间对象) | 较低 | 最低 |
| JVM优化 | 字符串常量池 | 无特殊优化 | 无特殊优化 |
| 继承关系 | 实现CharSequence |
继承AbstractStringBuilder |
继承AbstractStringBuilder |
二、底层源码深度解析
1. String的不可变性(核心设计)
java
复制
下载
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
// ✅ 关键:final修饰的char数组
private final char value[];
// 每次"修改"都创建新对象
public String concat(String str) {
// ... 创建新char数组,复制数据
return new String(buf, true);
}
}
不可变性的好处:
-
线程安全:天然线程安全
-
安全性:适合作为Map键值、网络连接参数等
-
缓存哈希码:只需计算一次,提高集合性能
-
字符串常量池:实现字符串复用,节省内存
2. StringBuilder和StringBuffer的共性
java
复制
下载
// 共同的父类
abstract class AbstractStringBuilder {
// ✅ 关键:非final的char数组
char[] value;
// 容量可动态扩容
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
value = Arrays.copyOf(value, newCapacity);
}
}
3. StringBuilder vs StringBuffer的关键差异
java
复制
下载
// StringBuilder(JDK 1.5+)
public final class StringBuilder extends AbstractStringBuilder {
// 方法没有同步
public StringBuilder append(String str) {
super.append(str);
return this;
}
}
// StringBuffer(JDK 1.0)
public final class StringBuffer extends AbstractStringBuilder {
// ✅ 关键:所有方法都有synchronized
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
public synchronized String toString() {
// 第一次调用时缓存结果
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
三、性能对比实测
测试代码示例
java
复制
下载
public class StringPerformanceTest {
private static final int LOOP_COUNT = 100000;
public static void main(String[] args) {
// 测试1:String拼接(最差)
long start1 = System.currentTimeMillis();
String str1 = "";
for (int i = 0; i < LOOP_COUNT; i++) {
str1 += "a"; // 每次循环创建新String对象
}
System.out.println("String耗时: " + (System.currentTimeMillis() - start1) + "ms");
// 测试2:StringBuilder(最优)
long start2 = System.currentTimeMillis();
StringBuilder sb2 = new StringBuilder();
for (int i = 0; i < LOOP_COUNT; i++) {
sb2.append("a"); // 原地修改
}
String result2 = sb2.toString();
System.out.println("StringBuilder耗时: " + (System.currentTimeMillis() - start2) + "ms");
// 测试3:StringBuffer(中等)
long start3 = System.currentTimeMillis();
StringBuffer sb3 = new StringBuffer();
for (int i = 0; i < LOOP_COUNT; i++) {
sb3.append("a"); // synchronized有开销
}
String result3 = sb3.toString();
System.out.println("StringBuffer耗时: " + (System.currentTimeMillis() - start3) + "ms");
}
}
典型测试结果(10万次拼接)
text
复制
下载
String耗时: 3850ms # 最慢,产生大量中间对象
StringBuilder耗时: 5ms # 最快,无锁开销
StringBuffer耗时: 15ms # 较慢,有同步开销
内存占用对比:
java
复制
下载
// String拼接:产生约10万个String对象 + 10万个char[]
// StringBuilder:只产生1个StringBuilder对象 + 少量扩容的char[]
// StringBuffer:同StringBuilder,但有锁对象开销
四、使用场景指南
优先使用String的场景 ✅
-
字符串常量或不需修改的情况
java
复制
下载
String url = "https://api.example.com"; String fileName = "config.properties"; -
作为HashMap的键
java
复制
下载
Map<String, User> userMap = new HashMap<>(); userMap.put("user001", new User()); // String不可变,保证键值稳定 -
安全敏感信息存储
java
复制
下载
String password = getPassword(); // 使用后可以快速置null,StringBuffer/StringBuilder需要遍历清空数组 -
多线程共享且不修改的字符串
java
复制
下载
public static final String DEFAULT_ENCODING = "UTF-8";
优先使用StringBuilder的场景 ✅
-
单线程下的字符串拼接
java
复制
下载
public String buildQuery(Map<String, String> params) { StringBuilder query = new StringBuilder(); query.append("SELECT * FROM users WHERE "); for (Map.Entry<String, String> entry : params.entrySet()) { query.append(entry.getKey()) .append("='") .append(entry.getValue()) .append("' AND "); } // 删除最后的" AND " query.delete(query.length() - 5, query.length()); return query.toString(); } -
循环体内的字符串操作
java
复制
下载
// 好的做法 StringBuilder result = new StringBuilder(); for (User user : userList) { result.append(user.getName()).append(","); } // 差的做法(性能差) String result = ""; for (User user : userList) { result += user.getName() + ","; } -
SQL/JSON/XML动态构建
java
复制
下载
public String toJson(List<User> users) { StringBuilder json = new StringBuilder("["); for (int i = 0; i < users.size(); i++) { json.append(users.get(i).toJsonString()); if (i < users.size() - 1) json.append(","); } json.append("]"); return json.toString(); }
优先使用StringBuffer的场景 ✅
-
多线程环境下的字符串操作
java
复制
下载
public class ThreadSafeLogger { private StringBuffer logBuffer = new StringBuffer(); // 多个线程可能同时调用此方法 public synchronized void log(String message) { logBuffer.append(Thread.currentThread().getName()) .append(": ") .append(message) .append("\n"); } public String getLog() { return logBuffer.toString(); } } -
全局共享的字符串缓冲区
java
复制
下载
public class SharedStringHolder { // 多个线程需要修改同一个字符串 private static StringBuffer sharedBuffer = new StringBuffer(); public static void appendSafe(String text) { sharedBuffer.append(text); // 线程安全 } }
五、高级技巧与最佳实践
1. 初始化容量优化
java
复制
下载
// 不好的做法:默认容量16,频繁扩容
StringBuilder sb1 = new StringBuilder();
sb1.append("很长的字符串..."); // 可能触发扩容
// 好的做法:预估容量,避免扩容开销
int estimatedLength = 1000;
StringBuilder sb2 = new StringBuilder(estimatedLength);
2. 链式调用优势
java
复制
下载
// StringBuilder/StringBuffer支持链式调用
String result = new StringBuilder()
.append("SELECT ")
.append(fields)
.append(" FROM ")
.append(tableName)
.append(" WHERE id=")
.append(id)
.toString();
3. JDK 9+的性能优化
java
复制
下载
// JDK 9后,String内部使用byte[]存储(Latin1字符用1字节)
// 但对外API保持不变,仍然不可变
// JDK 9的字符串拼接优化
String a = "Hello";
String b = "World";
String c = a + b; // 编译器可能优化为invokedynamic
4. 性能敏感场景的特殊处理
java
复制
下载
// 超高性能场景:直接操作char[]
public char[] toCharArrayDirect() {
char[] result = new char[length];
System.arraycopy(value, 0, result, 0, length);
return result;
}
// 使用ThreadLocal避免StringBuilder重复创建
private static final ThreadLocal<StringBuilder> threadLocalStringBuilder =
ThreadLocal.withInitial(() -> new StringBuilder(512));
六、面试深度回答要点
基础回答(校招/初级)
"String是不可变的,每次修改都创建新对象;StringBuffer和StringBuilder是可变的。StringBuffer是线程安全的但性能较低,StringBuilder线程不安全但性能最高。单线程用StringBuilder,多线程用StringBuffer。"
进阶回答(社招/中级)
"从源码看,String的不可变性由final char[]保证,这带来了线程安全、缓存哈希码、字符串常量池等好处。StringBuffer通过synchronized实现线程安全,有锁开销;StringBuilder无锁。在性能敏感场景要预估容量,避免扩容开销。JDK 9后String内部改用byte[]存储以节省内存。"
深度回答(高级/架构师)
"这本质上是不可变对象 与可变对象的设计哲学差异。String的不可变性牺牲了修改性能,但换来了安全性和可预测性,适合作为系统边界的数据载体。StringBuilder采用可变设计+无锁,实现了极致性能。在实际架构中,我们通常用String作为DTO、配置项,用StringBuilder处理模板渲染、SQL构建,用StringBuffer处理多线程日志拼接。在微服务间传递数据时,String的不可变性还能防止意外的数据修改。"
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
高频扩展问题
-
String str = "a" + "b" + "c"创建了几个对象?
- 编译期优化后只创建1个:"abc"(常量折叠)
-
new String("abc")创建了几个对象?
- 可能1个或2个:常量池中的"abc"(如有)+ 堆中的新String对象
-
StringBuilder的默认容量和扩容规则?
- 默认16字符,扩容:newCapacity = oldCapacity * 2 + 2
-
为什么StringBuffer的toString()有缓存?
- 防止多次调用toString()重复创建char[],但toStringCache只在toString()中有效
-
String的intern()方法作用?
- 将字符串放入常量池,相同内容的字符串返回同一个引用
掌握这三者的区别,不仅是为了面试,更是在实际开发中写出高性能、可维护代码的基础。根据场景选择合适的工具,是每个Java工程师的基本功。