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


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

在 Java 中,StringBuilderStringBuffer 都是用来处理可变字符串的类,它们的主要功能是通过动态拼接字符串来避免 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 通过在关键方法(如 appenddelete)上加 synchronized 锁,确保多线程环境下对底层字符数组的修改是原子的,避免数据不一致。
    • 源码支持:见上述 append 方法的 synchronized 关键字。
  2. "所有方法都加锁吗?有什么例外?"

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

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

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

四、总结回答思路

  1. 结构化表达:先讲核心区别(线程安全、性能),再深入源码(方法实现、锁使用),最后结合问题延伸。
  2. 源码支撑 :引用具体方法(如 appendtoString),突出加锁与否的差异。
  3. 场景导向 :强调选择依据(单线程用 StringBuilder,多线程用 StringBuffer 或加同步)。
  4. 灵活应对:预判面试官提问,准备简洁有力的回答,展示对底层实现的理解。
相关推荐
来自星星的坤4 小时前
SpringBoot 与 Vue3 实现前后端互联全解析
后端·ajax·前端框架·vue·springboot
AUGENSTERN_dc5 小时前
RaabitMQ 快速入门
java·后端·rabbitmq
烛阴5 小时前
零基础必看!Express 项目 .env 配置,开发、测试、生产环境轻松搞定!
javascript·后端·express
燃星cro5 小时前
参照Spring Boot后端框架实现序列化工具类
java·spring boot·后端
追逐时光者8 小时前
C#/.NET/.NET Core拾遗补漏合集(25年4月更新)
后端·.net
FG.8 小时前
GO语言入门
开发语言·后端·golang
转转技术团队9 小时前
加Log就卡?不加Log就瞎?”——这个插件治好了我的精神
java·后端
谦行9 小时前
前端视角 Java Web 入门手册 5.5:真实世界 Web 开发——控制反转与 @Autowired
java·后端
uhakadotcom9 小时前
PyTorch 2.0:最全入门指南,轻松理解新特性和实用案例
后端·面试·github
bnnnnnnnn9 小时前
前端实现多服务器文件 自动同步宝塔定时任务 + 同步工具 + 企业微信告警(实战详解)
前端·javascript·后端