哈喽,刷题小分队集合!Day2 的 == 和 equals 考点是不是让你对 Java 的 “比较逻辑” 更清晰了?今天咱们攻克另一个面试高频题 ——String、StringBuffer、StringBuilder 的核心区别,这题不仅考定义,还常问性能对比和实际场景选择,赶紧往下看!​

今日题目​

  1. String 为什么是不可变的?底层实现原理是什么?​
  1. StringBuffer 和 StringBuilder 的核心区别是什么?​
  1. 三者的性能排序如何?分别适用于什么场景?​

一、基础答案​

  1. String:不可变字符序列​
  • 底层是 final char[] 数组(JDK9 后改为 byte[],按编码优化存储),final 修饰的数组无法修改引用,且 String 类没有提供修改数组内容的方法;​
  • 每次对 String 做拼接、替换等操作,都会新建一个 String 对象(原对象不变),如 String s = "a"; s += "b"; 会生成 3 个对象("a"、"b"、"ab")。​
  1. StringBuffer:线程安全的可变字符序列​
  • 底层是 char[] 数组(默认容量 16),支持动态扩容;​
  • 所有修改方法(如 append()、insert())都加了 synchronized 锁,保证多线程环境下安全。​
  1. StringBuilder:非线程安全的可变字符序列​
  • 底层实现和 StringBuffer 一致(char[] 数组 + 动态扩容);​
  • 未加锁,性能比 StringBuffer 高,但多线程环境下可能出现数据错乱。​
  1. 核心区别总结表​

| 特性 | String | StringBuffer | StringBuilder |​

|--------------|-----------------------|-----------------------|-----------------------|​

| 可变性 | 不可变 | 可变 | 可变 |​

| 线程安全 | 安全(无修改方法) | 安全(synchronized 锁)| 不安全 |​

| 底层实现 | final char []/byte [] | char [] | char [] |​

| 性能 | 最低(频繁创建对象) | 中等(锁开销) | 最高(无锁) |​

| 适用场景 | 字符串不频繁修改 | 多线程 + 字符串修改 | 单线程 + 字符串修改 |​

二、深度拆解(面试加分项)​

1. String 不可变的底层细节(JDK8 vs JDK9)​

  • JDK8 及之前:private final char value[],每个字符占 2 字节(UTF-16),无论中英文;​
  • JDK9 优化:private final byte[] value,搭配 coder 字段标记编码(LATIN1 占 1 字节,UTF-16 占 2 字节),英文场景更省内存。​

不可变的好处:​

  • 线程安全:无需同步即可在多线程中共享;​
  • 缓存友好:字符串常量池可复用相同内容的对象(如 String s1="a"; String s2="a"; 指向同一个对象);​
  • 哈希值稳定:不可变对象的 hashCode 计算后可缓存,提升 HashMap 等集合的查询效率。​

2. StringBuffer/StringBuilder 的扩容机制​

默认初始容量为 16,当添加的内容超过当前容量时,扩容逻辑:

// 简化扩容源码(StringBuffer 和 StringBuilder 逻辑一致)​
private void ensureCapacityInternal(int minimumCapacity) {​
    if (minimumCapacity - value.length > 0) {​
        value = Arrays.copyOf(value, ​
            newCapacity(minimumCapacity)); // 新容量 = 旧容量*2 + 2​
    }​
}

优化建议:如果提前知道字符串长度(如拼接 100 个字符),可直接指定初始容量(如 new StringBuilder(100)),避免多次扩容(数组拷贝耗时)。​

3. 性能对比实测(面试可直接说结论)​

用 10 万次字符串拼接测试:​

  • String:约 3000ms(频繁创建对象,GC 开销大);​
  • StringBuffer:约 10ms(锁开销存在,但比 String 快几个数量级);​
  • StringBuilder:约 5ms(无锁,性能最优)。​

结论:单线程场景优先用 StringBuilder,多线程用 StringBuffer,String 仅用于无需修改的字符串。​

三、面试避坑提醒​

  1. 避免在循环中用 String += 拼接字符串,会生成大量临时对象,建议用 StringBuilder;​
  1. StringBuffer 的 synchronized 是对象锁,多线程操作同一个 StringBuffer 才安全,不同对象则无锁竞争;​
  1. JDK9 后 String 底层改为 byte[],但不可变性的核心逻辑没变,面试时可主动提及这个优化,体现专业性;​
  1. 注意:String s = "a" + "b" + "c"; 编译期会优化为 String s = "abc";,不会产生临时对象(编译器常量折叠)。​

四、今日打卡​

评论区留下你的答案:以下代码执行后,创建了几个 String 对象?✅

String s1 = "Java";​
String s2 = "Java";​
String s3 = new String("Java");​
String s4 = s1 + "面试";

(提示:结合常量池和字符串拼接的优化逻辑分析)​

文末预告​

Day4 预告:Java 异常体系大梳理!Checked Exception 和 Unchecked Exception 的区别?try-catch-finally 如何正确使用?明天继续刷题~

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐