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. 灵活应对:预判面试官提问,准备简洁有力的回答,展示对底层实现的理解。
相关推荐
Apifox几秒前
Apifox 11 月更新|AI 生成测试用例能力持续升级、JSON Body 自动补全、支持为响应组件添加描述和 Header
前端·后端·测试
有风6313 分钟前
双向循环带头链表详解
后端
找不到对象就NEW一个21 分钟前
用wechatapi进行微信二次开发,微信api
后端
charlie11451419121 分钟前
勇闯前后端Week2:后端基础——Flask API速览
笔记·后端·python·学习·flask·教程
有风6328 分钟前
基于顺序表完成通讯录项目
后端
yuuki23323329 分钟前
【C++】初识C++基础
c语言·c++·后端
q***876036 分钟前
springboot下使用druid-spring-boot-starter
java·spring boot·后端
程序员西西37 分钟前
SpringBoot无感刷新Token实战指南
java·开发语言·前端·后端·计算机·程序员
南雨北斗41 分钟前
mysql视图的作用
后端
Pa2sw0rd丶44 分钟前
Fastjson 反序列化漏洞深度解析:从原理到实战防护
java·后端·安全