JavaSE-字符串-String、StringBuffer、StringBuilder

40 阅读3分钟

What (本质区别)

特性StringStringBufferStringBuilder
可变性不可变(final修饰)可变可变
线程安全✅ 天然线程安全(不可变)同步方法(synchronized)非同步(无锁)
JVM存储常量池(字面量)/堆(new)堆内存(char[]数组)堆内存(char[]数组)
适用场景少量字符串操作、常量多线程环境下的字符串拼接单线程环境下的字符串拼接

💡 通俗比喻
String刻在石头上的字(要改只能重刻一块石头)
StringBuffer带锁的记事本(多个人用,每次只能一个人写,安全但慢)
StringBuilder无锁的记事本(一个人用,想写就写,快但不安全)
JVM图书管理员(为不同场景提供合适的工具)


How (机制深度解析)

jvm实现机制

// String:每次操作都创建新对象
String str = "hello"; // 常量池
str += " world";      // 创建新String对象,原对象不变

// StringBuffer:内部char[]扩容 + 同步锁
public synchronized StringBuffer append(String str) {
    toStringCache = null; // 清除缓存
    super.append(str);    // 调用父类方法
    return this;
}

// StringBuilder:无锁版本,直接操作char[]
@Override
public StringBuilder append(String str) {
    super.append(str);    // 无synchronized修饰
    return this;
}

性能对比 (100万字符拼接解析)

类型内存占用GC次数原因
String~400MB25次每次拼接创建新对象,旧对象成为垃圾
StringBuffer~2MB1次内部char[]动态扩容,对象复用
StringBuilder~2MB1次同StringBuffer,但无同步开销

💡 关键机制解释
动态扩容:像伸缩水管,空间不够时自动扩容(通常1.5倍或2倍)
同步锁开销:StringBuffer每个方法都加synchronized,像每次进门都要刷卡验证
逃逸分析:JVM对StringBuilder进行优化,可能将其分配在栈上而非堆上,避免GC
常量折叠:编译器将"a" + "b" + "c"优化为"abc"String也能高效


Why(选择策略)

性能优先级

// ✅ 最佳实践:单线程用StringBuilder
public String buildReport(List<String> data) {
    StringBuilder sb = new StringBuilder(1024); // 预估大小
    for (String item : data) {
        sb.append(item).append("\n");
    }
    return sb.toString();
}

// ✅ 多线程用StringBuffer
private static final StringBuffer logBuffer = new StringBuffer();
public static synchronized void log(String message) {
    logBuffer.append(LocalDateTime.now()).append(": ").append(message).append("\n");
}

// ✅ 少量操作用String(编译器会优化)
String message = "User: " + userName + ", Action: " + action; // 编译期优化为StringBuilder

选择决策树

image.png

jvm优化机会

  • StringBuilder逃逸分析:JVM检测到StringBuilder不逃逸出方法,可能将其分配在栈上,完全避免GC
  • StringBuffer锁消除:JVM检测到StringBuffer只在单线程使用,可能消除同步锁
  • 编译期优化"a" + "b" + "c"在编译期直接合并为"abc"运行时零开销

常见错误

// ❌ 陷阱1:在循环中用String拼接
String result = "";
for (int i = 0; i < 10000; i++) {
    result += i; // 每次创建新对象,性能灾难
}

// ❌ 陷阱2:多线程用StringBuilder
public class UnsafeCounter {
    private StringBuilder count = new StringBuilder("0");
    
    public void increment() {
        count.append(", ").append(System.currentTimeMillis()); // 线程不安全!
    }
}

// ✅ 修复1:循环用StringBuilder
StringBuilder sb = new StringBuilder(10000);
for (int i = 0; i < 10000; i++) {
    sb.append(i);
}

// ✅ 修复2:多线程用StringBuffer或加锁
private final StringBuffer safeCount = new StringBuffer("0");
public synchronized void increment() {
    safeCount.append(", ").append(System.currentTimeMillis());
}

💡核心总结

类型核心价值使用频率记忆口诀
String安全、简单、不可变⭐⭐⭐⭐⭐"少拼少接,安全第一"
StringBuffer多线程安全⭐⭐"多人共享,锁保平安"
StringBuilder单线程极致性能⭐⭐⭐⭐"一人专用,快如闪电"

JVM层面的真相

  • String的"快":只在编译期常量折叠时快,运行时拼接最慢
  • StringBuffer的"慢":主要是同步锁开销,无竞争时JVM会优化
  • StringBuilder的"快":无锁 + JVM逃逸分析 + 栈上分配 = 接近C语言性能

一句话本质
String是安全的基石,StringBuffer是线程安全的盾牌,StringBuilder是性能的利剑