StringBuffer 与 StringBuilder 的源码分析与差异

StringBuffer 与 StringBuilder 的源码分析与差异

在 Java 开发中,StringBufferStringBuilder 是两个常用的字符串操作类,尤其在需要频繁拼接字符串时,它们比直接使用 String 更高效。本文将从源码角度深入分析两者的差异,探讨它们的实现原理及 API,并总结适用场景。

1. 基本概述

  • StringBuffer:线程安全的可变字符串类,诞生于 JDK 1.0,主要用于多线程环境下。
  • StringBuilder:非线程安全的可变字符串类,引入于 JDK 1.5,适用于单线程场景,性能更高。

两者的核心功能都是通过字符数组实现字符串的动态拼接,但线程安全性和性能是它们的主要区别。

2. 源码分析

2.1 继承关系与基本结构

StringBufferStringBuilder 都继承自抽象类 AbstractStringBuilder,后者封装了字符数组操作的核心逻辑:

java 复制代码
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    char[] value; // 存储字符的数组
    int count;    // 当前字符数
    // ...
}
  • value 是底层的字符数组,初始容量默认 16(无参构造)。
  • count 表示当前已使用的字符数。

StringBufferStringBuilder 只是覆写了部分方法,添加了线程安全相关的控制。

2.2 构造方法

两者的构造方法类似,默认容量为 16,源码如下:

java 复制代码
// StringBuffer
public StringBuffer() {
    super(16);
}

// StringBuilder
public StringBuilder() {
    super(16);
}

如果指定初始字符串,会在字符串长度基础上再加 16:

java 复制代码
public StringBuffer(String str) {
    super(str.length() + 16);
    append(str);
}

2.3 核心方法:append

append 是字符串拼接的核心方法,源码在 AbstractStringBuilder 中实现:

java 复制代码
public AbstractStringBuilder append(String str) {
    if (str == null) str = "null";
    int len = str.length();
    ensureCapacityInternal(count + len); // 确保容量足够
    str.getChars(0, len, value, count);  // 复制字符
    count += len;
    return this;
}
  • 容量扩展 :如果当前数组容量不足,会调用 ensureCapacityInternal 进行扩容,扩容策略为 (value.length << 1) + 2,即当前容量翻倍加 2。
  • 字符复制 :通过 getChars 将新字符串追加到 value 数组。
StringBuffer 的线程安全

StringBufferappend 方法加了 synchronized 关键字:

java 复制代码
public synchronized StringBuffer append(String str) {
    toStringCache = null; // 清除缓存
    super.append(str);
    return this;
}

每次操作都会加锁,确保多线程环境下数据一致性。

StringBuilder 的非线程安全

StringBuilderappend 直接调用父类实现,没有同步:

java 复制代码
public StringBuilder append(String str) {
    super.append(str);
    return this;
}

少了锁的开销,因此性能更高。

2.4 toString 方法

  • StringBuffer :维护一个 toStringCache 字段缓存上次的 toString 结果,只有在修改后才会重建:
java 复制代码
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache);
}
  • StringBuilder:直接构造新字符串,无缓存:
java 复制代码
public String toString() {
    return new String(value, 0, count);
}

StringBuffer 的缓存设计是为了减少多线程环境下重复构造的开销,但实际性能提升有限。

3. API 对比

两者的 API 几乎一致,主要包括:

  • append:追加字符串、字符等,支持多种重载。
  • insert:在指定位置插入数据。
  • delete:删除指定范围的字符。
  • replace:替换指定范围的字符。
  • reverse:反转字符串。
  • substring:提取子字符串。

区别在于:StringBuffer 的所有公共方法几乎都加了 synchronized,而 StringBuilder 没有。

4. 性能与适用场景

  • 线程安全
    • StringBuffer:适合多线程环境,但因同步锁开销较大,性能较低。
    • StringBuilder:非线程安全,单线程环境下效率更高。
  • 性能测试 (示例): 在单线程下,循环 10 万次拼接字符串:
    • StringBuilder:约 10ms
    • StringBuffer:约 15ms 差距主要来自锁的开销。

5. 总结

  • 选择依据
    • 多线程环境或遗留代码:用 StringBuffer
    • 单线程或性能敏感场景:用 StringBuilder
  • 源码差异
    • StringBuffer 通过 synchronized 实现线程安全,增加了性能开销。
    • StringBuilder 去掉同步,逻辑更轻量。
  • 建议 :现代开发中,单线程场景占主导,StringBuilder 是首选。若需线程安全,可结合 Collections.synchronizedListLock 实现更灵活的控制。
相关推荐
qq_297574678 小时前
【实战】POI 实现 Excel 多级表头导出(含合并单元格完整方案)
java·spring boot·后端·excel
郝学胜-神的一滴8 小时前
超越Spring的Summer(一): PackageScanner 类实现原理详解
java·服务器·开发语言·后端·spring·软件构建
Tony Bai8 小时前
“Go 2,请不要发生!”:如果 Go 变成了“缝合怪”,你还会爱它吗?
开发语言·后端·golang
Victor3568 小时前
Hibernate(91)如何在数据库回归测试中使用Hibernate?
后端
Victor3569 小时前
MongoDB(1)什么是MongoDB?
后端
Victor35615 小时前
https://editor.csdn.net/md/?articleId=139321571&spm=1011.2415.3001.9698
后端
Victor35615 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
后端
灰子学技术17 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
Gogo81618 小时前
BigInt 与 Number 的爱恨情仇,为何大佬都劝你“能用 Number 就别用 BigInt”?
后端
fuquxiaoguang18 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析