StringBuilder 和 StringBuffer 的区别:源码分析与面试准备


StringBuilder 和 StringBuffer 的区别:源码分析与面试准备

在 Java 中,StringBuilder 和 StringBuffer 都是用来处理可变字符串的类,它们的主要功能是通过动态拼接字符串来避免 String 的不可变性带来的性能开销。尽管它们功能相似,但在实现细节和使用场景上有显著差异。以下从源码角度分析两者的区别,并探讨"是否所有方法都加了锁"这个问题。

一、核心区别

  1. 线程安全性

    • StringBuffer :线程安全。它的绝大部分方法都通过 synchronized 关键字加了锁,以确保在多线程环境下操作的安全性。
    • StringBuilder:非线程安全。它没有加锁机制,因此在单线程环境下性能更高,但在多线程场景中需要外部同步。
  2. 性能

    • StringBuffer 的同步机制带来了额外的性能开销,尤其在高并发场景下。
    • StringBuilder 没有同步开销,适合单线程或无需线程安全的场景。
  3. 继承关系

    • 两者都继承自 AbstractStringBuilder 类,共享相同的底层实现(如字符数组存储、容量扩展逻辑)。区别主要在于 StringBuffer 覆写了方法并加锁,而 StringBuilder 直接复用父类逻辑。

二、源码分析

让我们从 JDK 源码(以 JDK 11 为例)中看看具体实现。

1. 构造方法
  • StringBuffer :

    java 复制代码
    public StringBuffer() {
        super(16); // 默认容量为 16
    }
  • StringBuilder :

    java 复制代码
    public StringBuilder() {
        super(16); // 默认容量为 16
    }

两者构造方法都调用了 AbstractStringBuilder 的构造方法,初始化一个默认容量为 16 的字符数组。区别不在构造,而在后续操作。

2. append 方法(最常用)
  • StringBuffer :

    java 复制代码
    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    • synchronized 确保线程安全。
    • 调用父类的 append,实际操作字符数组。
  • StringBuilder :

    java 复制代码
    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    • 没有 synchronized,直接调用父类实现。
3. delete 方法
  • StringBuffer :

    java 复制代码
    @Override
    public synchronized StringBuffer delete(int start, int end) {
        toStringCache = null;
        super.delete(start, end);
        return this;
    }
  • StringBuilder :

    java 复制代码
    public StringBuilder delete(int start, int end) {
        super.delete(start, end);
        return this;
    }
4. 是否所有方法都加锁?
  • StringBuffer :并非所有方法都加锁。例如:
    • toString():

      java 复制代码
      @Override
      public synchronized String toString() {
          if (toStringCache == null) {
              toStringCache = Arrays.copyOfRange(value, 0, count);
          }
          return new String(toStringCache, 0, toStringCache.length);
      }

      toString() 加了锁,但 length() 和 capacity() 等方法未加锁:

      java 复制代码
      public int length() {
          return count;
      }

      原因是这些方法只读取状态,不修改底层数据,不涉及线程竞争。

  • StringBuilder:无任何方法加锁,全部依赖父类实现。

三、面试官可能提出的问题及回答

  1. "为什么 StringBuffer 是线程安全的?"

    • 回答:StringBuffer 通过在关键方法(如 append、delete)上加 synchronized 锁,确保多线程环境下对底层字符数组的修改是原子的,避免数据不一致。
    • 源码支持:见上述 append 方法的 synchronized 关键字。
  2. "所有方法都加锁吗?有什么例外?"

    • 回答:不是所有方法都加锁。StringBuffer 的修改方法(如 append、insert)加了锁,但只读方法(如 length、capacity)未加锁,因为它们不改变状态,无线程安全问题。
    • 思路:区分读写操作,结合源码举例。
  3. "StringBuilder 和 StringBuffer 的性能差距有多大?"

    • 回答:在单线程环境下,StringBuilder 因为无同步开销,性能更高。差距取决于操作频率和规模,在高并发场景下 StringBuffer 的锁竞争可能导致显著延迟。
    • 思路:从 JVM 锁机制(如锁膨胀)延伸,强调场景选择。
  4. "如果在多线程中用 StringBuilder 会怎样?"

    • 回答:可能导致数据混乱或异常(如数组越界),因为多个线程同时修改共享的字符数组无同步保护。可以用 synchronized 块或 StringBuffer 替代。
    • 思路:举例说明竞争条件,结合实际场景。

四、总结回答思路

  1. 结构化表达:先讲核心区别(线程安全、性能),再深入源码(方法实现、锁使用),最后结合问题延伸。
  2. 源码支撑 :引用具体方法(如 append、toString),突出加锁与否的差异。
  3. 场景导向 :强调选择依据(单线程用 StringBuilder,多线程用 StringBuffer 或加同步)。
  4. 灵活应对:预判面试官提问,准备简洁有力的回答,展示对底层实现的理解。
相关推荐
程序猿DD3 小时前
OctaFuse Gateway 2.15.0:MCP 聚合、向量 Embeddings 与官方目录同步
后端·agent
余槐i6 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8266 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
凤山老林7 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne8 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF9 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
pride.li9 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
upala10 小时前
【仓颉语言入门 · 第二篇 第 6 课】
后端
罗丹丹Luna10 小时前
移动端显示“已提交”以后:SAP 与低代码集成该怎样暴露真实状态
后端·架构
码事漫谈10 小时前
因为太想讨好你,所以AI会骗你
后端