当前位置: 首页 > news >正文

StringBuilder类的数据结构和扩容方式解读

目录

StringBuilder是什么

核心特性:

StringBuilder数据结构

1. 核心存储结构(基于父类 AbstractStringBuilder)

2. 类定义与继承关系

3. 数据结构的核心特点

StringBuilder数据结构的初始化方式

1. 无参构造:默认初始容量为 16

2. 指定初始容量:自定义数组大小

3. 基于已有字符串初始化:容量 = 字符串长度 + 16

初始化的核心逻辑

在append()追加时,如果容量不足,如何扩容

1. 计算所需的最小容量

2. 判断是否需要扩容

3. 确定新的容量

4. 创建新数组并复制数据

5. 更新相关属性

源码解析如下:




深入理解 String 类的不可变性及其内部数据结构_Java字符串拼接性能优化-CSDN博客

小编在这篇博客中讲过,当我们对一个String对象进行修改(如拼接、替换等)时,原对象的内容不会被改变,JVM 会创建一个全新的String对象来存储修改后的结果,并将变量引用指向新对象。

String类型的不可变保障线程安全,优化哈希性能,支持常量池复用以及确保数据安全,但在需要频繁修改字符串的场景中(例如循环拼接大量数据),会产生大量临时对象,不仅占用额外内存,还会增加垃圾回收的负担,导致性能显著下降。而StringBuilder的出现弥补了String类这一缺点,今天,我们就来深入解读StringBuilder类的数据结构与扩容方式,看看它是如何高效应对字符串的动态变化的。

StringBuilder是什么

StringBuilder 是 Java 中用于处理可变字符序列的类,主要用于高效地进行字符串的动态构建和修改,是为解决 String 类在频繁字符串操作时的性能问题而设计的。

核心特性:

  1. 可变性
    这是 StringBuilder 最核心的特性。与 String 的不可变性不同,StringBuilder 的内部字符序列可以直接修改(如拼接、插入、删除等),所有操作都在原有对象上进行,不会像 String 那样每次修改都创建新对象,从而大幅减少内存消耗和垃圾回收压力。

  2. 高效的字符串操作
    提供了丰富的操作方法,如 append()(追加任意类型数据)、insert()(指定位置插入)、delete()(删除区间字符)、replace()(替换字符)等,这些方法均针对性能优化设计,执行效率远高于 String 的同类操作(尤其在循环拼接等场景中)。

  3. 非线程安全
    StringBuilder 的方法没有添加 synchronized 同步锁,因此在多线程环境下并发修改可能导致数据不一致。但这也使其在单线程场景中性能优于线程安全的 StringBuffer(避免了同步带来的开销)。

  4. 动态扩容机制
    内部基于字符数组(Java 9 后优化为字节数组,根据字符编码动态调整存储方式)存储数据,初始容量可自定义(默认 16)。当追加内容导致容量不足时,会自动扩容(通常为原容量的 2 倍加 2),并复制原有数据到新数组,平衡了内存占用和操作效率。

  5. 与 String 的便捷转换
    可通过 toString() 方法快速转换为 String 对象,满足需要 String 类型参数的场景,转换过程会创建新的 String 对象,但仅执行一次,性能开销可控。

// 创建对象
StringBuilder sb = new StringBuilder();// 追加内容
sb.append("Hello");
sb.append(" ");
sb.append("World");// 插入内容
sb.insert(5, ","); // 在索引5处插入逗号// 转换为String
String result = sb.toString(); // 结果:"Hello, World"

StringBuilder数据结构

1. 核心存储结构(基于父类 AbstractStringBuilder

StringBuilder 本身不直接定义存储结构,而是继承自抽象类 AbstractStringBuilder,核心数据存储由父类维护,主要包含两个关键成员变量:

  • 存储字符的数组

    • Java 8 及之前:使用 char[] value 数组,每个字符固定占用 2 个字节(UTF-16 编码),直接存储字符序列。
    • Java 9 及之后:优化为 byte[] value 数组,根据字符类型动态选择编码:
      • 若字符为 ASCII 或 Latin-1 范围内(单字节可表示),则用 1 字节存储,节省内存;
      • 若包含多字节字符(如中文、日文),则用 2 字节存储(UTF-16 编码)。
  • 字符长度计数器int count,记录当前存储的有效字符数量(而非数组容量)。

2. 类定义与继承关系

StringBuilder 的类定义简化如下(省略部分接口实现):

// 继承 AbstractStringBuilder,实现字符序列和可追加接口
public final class StringBuilder extends AbstractStringBuilder implements CharSequence, Appendable {// 构造方法(调用父类构造)public StringBuilder() {super(16); // 默认初始容量 16}public StringBuilder(int capacity) {super(capacity); // 指定初始容量}public StringBuilder(String str) {super(str.length() + 16); // 初始容量 = 字符串长度 + 16append(str);}// 其他方法(如 append、insert 等,实际调用父类方法)
}// 父类 AbstractStringBuilder 的核心定义
abstract class AbstractStringBuilder implements Appendable, CharSequence {// Java 9+ 用 byte[],Java 8 及之前用 char[]byte[] value; int count; // 有效字符数// 父类构造方法(初始化数组容量)AbstractStringBuilder(int capacity) {if (capacity < 0)throw new NegativeArraySizeException();value = new byte[capacity]; // 初始化数组}
}

3. 数据结构的核心特点

  • 数组存储:通过连续的数组结构存储字符,支持快速随机访问(如通过索引获取字符)。
  • 容量与长度分离value 数组的长度是「容量」(可容纳的最大字符数),count 是「实际长度」(已存储的字符数),容量 ≥ 长度。
  • 动态适配:Java 9+ 的 byte[] 优化减少了单字节字符的内存占用,平衡了存储效率和兼容性。

StringBuilder数据结构的初始化方式

StringBuilder 内部数据结构(字符数组)的初始化方式由其构造方法决定,主要通过以下 3 种方式指定初始容量,以适配不同场景的字符串操作需求:

1. 无参构造:默认初始容量为 16

当使用无参构造方法 new StringBuilder() 时,会调用父类 AbstractStringBuilder 的构造方法,初始化一个容量为 16 的字符数组(Java 8 及之前为 char[],Java 9+ 为 byte[])。

StringBuilder sb = new StringBuilder(); 
// 内部数组初始容量为 16,可直接存储 16 个字符(或更多,取决于编码)

2. 指定初始容量:自定义数组大小

如果已知字符串的大致长度,可通过 new StringBuilder(int capacity) 手动指定初始容量,避免后续频繁扩容。父类会根据传入的 capacity 初始化对应大小的数组。

StringBuilder sb = new StringBuilder(100); 
// 内部数组初始容量为 100,适合预计存储约 100 个字符的场景

3. 基于已有字符串初始化:容量 = 字符串长度 + 16

当传入一个 String 对象作为参数(new StringBuilder(String str))时,初始容量会设为「字符串长度 + 16」,既容纳原有字符串,又预留 16 个字符的空间供后续追加。同时会将传入的字符串内容复制到内部数组中。

String str = "hello"; // 长度为 5
StringBuilder sb = new StringBuilder(str); 
// 内部数组初始容量 = 5 + 16 = 21,且已存储 "hello" 这 5 个字符

初始化的核心逻辑

无论哪种方式,最终都是通过父类 AbstractStringBuilder 的构造方法完成数组初始化:

// 父类 AbstractStringBuilder 的构造方法(简化)
AbstractStringBuilder(int capacity) {if (capacity < 0) {throw new NegativeArraySizeException();}// 初始化数组(Java 8 为 char[],Java 9+ 为 byte[])value = new byte[capacity]; // 以 Java 9+ 为例
}

通过合理选择初始化方式(尤其是指定初始容量),可以减少后续 append() 操作时的扩容次数,进一步提升 StringBuilder 的性能。这种结构设计使得 StringBuilder 既能直接修改原有数据(实现可变性),又能通过数组的连续内存特性保证操作效率,是其高性能的核心基础。

在append()追加时,如果容量不足,如何扩容

当使用 StringBuilder 的 append() 方法追加字符或字符串时,如果当前内部字符数组的剩余容量不足以容纳新的数据,就会触发扩容操作。StringBuilder 扩容的具体流程如下(以 Java 8 及之后版本为例,Java 9 后对存储方式有优化,但扩容逻辑主体一致 ):

1. 计算所需的最小容量

在追加数据之前,StringBuilder 会先计算追加新数据后总共需要的字符数量,将其记为 minCapacity 。这个值是在原有有效字符数量(由 count 变量记录)的基础上,加上即将追加的数据的字符长度。

2. 判断是否需要扩容

将计算得到的 minCapacity 与当前字符数组(value)的容量(即 value.length )进行比较。如果 minCapacity 大于当前数组的容量,就意味着当前数组无法容纳新追加的数据,此时需要进行扩容操作。

3. 确定新的容量

StringBuilder 采用一种简单的扩容策略来确定新的数组容量,具体的计算方式是将当前容量乘以 2 再加 2 ,即

newCapacity = oldCapacity * 2 + 2

例如,如果当前数组的容量 oldCapacity 为 16,那么扩容后的新容量 newCapacity 就是 16 * 2 + 2 = 34 。

不过,如果通过上述计算得到的 newCapacity 仍然小于 minCapacity ,则会直接将 newCapacity 设置为 minCapacity ,以确保新数组能够容纳追加后的数据。

4. 创建新数组并复制数据

确定了新的容量 newCapacity 后,StringBuilder 会创建一个新的字符数组(char[] ),其长度为 newCapacity 。然后,通过系统调用(System.arraycopy() )将原数组中的数据复制到新数组中。这一步保证了原有的字符序列不会丢失,并且新数组有足够的空间来存储即将追加的数据。

5. 更新相关属性

复制完成后,StringBuilder 会将内部用于存储字符的数组引用(value )指向新创建的数组,同时更新其他可能相关的属性(例如在一些优化的实现中,可能会涉及到对编码标识等属性的调整 ),使得后续的追加操作可以在新的、更大容量的数组上进行。

源码解析如下:

abstract class AbstractStringBuilder implements Appendable, CharSequence {// 追加字符的方法public AbstractStringBuilder append(char c) {ensureCapacityInternal(count + 1);putChar(count++, c);return this;}// 追加字符串的方法public AbstractStringBuilder append(String str) {if (str == null)return appendNull();int len = str.length();ensureCapacityInternal(count + len);str.getBytes(coder, 0, len, value, count);count += len;return this;}// 确保内部容量足够的方法private void ensureCapacityInternal(int minimumCapacity) {// overflow-conscious codeif (minimumCapacity - value.length > 0)expandCapacity(minimumCapacity);}// 执行扩容的方法void expandCapacity(int minimumCapacity) {int newCapacity = value.length * 2 + 2;if (newCapacity - minimumCapacity < 0)newCapacity = minimumCapacity;if (newCapacity < 0) {if (minimumCapacity < 0) // overflowthrow new OutOfMemoryError();newCapacity = Integer.MAX_VALUE;}value = Arrays.copyOf(value, newCapacity);}
}
  • append(char c) 和 append(String str) 方法在追加数据前,都会先调用 ensureCapacityInternal(int minimumCapacity) 方法,计算追加数据后所需的最小容量minimumCapacity,并判断当前数组容量是否足够。
  • 如果当前容量不足,ensureCapacityInternal 方法会调用 expandCapacity(int minimumCapacity) 进行扩容。expandCapacity 方法先按照 newCapacity = oldCapacity * 2 + 2 的规则计算新容量newCapacity,如果计算出的新容量小于minimumCapacity,则将newCapacity设置为minimumCapacity 。同时,还会处理新容量溢出的情况,若溢出且minimumCapacity小于 0,抛出OutOfMemoryError,否则将newCapacity设置为Integer.MAX_VALUE 。最后通过Arrays.copyOf方法将原数组数据复制到新的、更大容量的数组中,完成扩容。

通过以上步骤,StringBuilder 实现了在容量不足时的自动扩容,从而保证了在频繁追加数据的情况下,仍然能够高效地进行字符串操作。

StringBuilder凭借其可变的字符序列设计、灵活的初始化方式以及智能的扩容机制,完美解决了String在频繁字符串操作时的性能瓶颈。从数据结构到扩容逻辑,每一处都为高效处理动态字符串而生,是 Java 中构建复杂字符串、提升程序性能的得力工具,尤其在大量字符串拼接等场景下,能显著优化内存使用与执行效率。

有问题欢迎留言!!!😗

肥嘟嘟左卫门就讲到这里啦,记得一键三连!!!😗


文章转载自:

http://VeVqt8Vk.nmpdm.cn
http://16QWVYiZ.nmpdm.cn
http://mYNQp6kC.nmpdm.cn
http://M3iqdyt7.nmpdm.cn
http://pva4xAV4.nmpdm.cn
http://qjdns5Qx.nmpdm.cn
http://vgnPh05L.nmpdm.cn
http://ihlGMZht.nmpdm.cn
http://3tuFmIiz.nmpdm.cn
http://4oLwqOvV.nmpdm.cn
http://DQVWbPDZ.nmpdm.cn
http://4wmBayXo.nmpdm.cn
http://wHAUpVBo.nmpdm.cn
http://vPAzvdgO.nmpdm.cn
http://iLJNcxzi.nmpdm.cn
http://Ym7JnZfT.nmpdm.cn
http://tbVkqxC7.nmpdm.cn
http://zOnfX2sJ.nmpdm.cn
http://aL5jIRbl.nmpdm.cn
http://iCItVA21.nmpdm.cn
http://0RW14nRi.nmpdm.cn
http://jzITs1ba.nmpdm.cn
http://YKUDVZ04.nmpdm.cn
http://Y6RWMS08.nmpdm.cn
http://mq88LvlK.nmpdm.cn
http://VnhZTDCG.nmpdm.cn
http://YuqZguBn.nmpdm.cn
http://lueKyv2j.nmpdm.cn
http://HHtLb5Rl.nmpdm.cn
http://SdxFg6mn.nmpdm.cn
http://www.dtcms.com/a/373470.html

相关文章:

  • SQL 层面行转列
  • XR数字融合工作站赋能新能源汽车专业建设的创新路径
  • 大模型(LLM)安全保障机制(技术、标准、管理)
  • 【LeetCode】String相关算法练习
  • Redis基本数据类型
  • 深度学习(三):监督学习与无监督学习
  • crew AI笔记[5] - knowledge和memory特性详解
  • MyBatis多数据库支持:独立 XML 方案与单文件兼容方案的优劣势分析及选型建议
  • 安卓玩机工具----安卓“搞机工具箱”最新版 控制手机的玩机工具
  • 100、23种设计模式之适配器模式(9/23)
  • Docker网络模式解析
  • ARM处理器基础
  • TDengine 选择函数 First 用户手册
  • 9.8网编基础知识day1
  • 卷积神经网络(CNN):从图像识别原理到实战应用的深度解析
  • 【LeetCode - 每日1题】将整数转换为两个无零整数的和
  • 【超详细图文教程】2025年最新 Jmeter 详细安装教程及接口测试示例
  • MongoDB 常见错误解决方案:从连接失败到主从同步问题
  • Guava中常用的工具类
  • Entity Digital Sports 降低成本并快速扩展
  • 计算机毕业设计选题:基于Spark+Hadoop的健康饮食营养数据分析系统【源码+文档+调试】
  • Rust异步运行时最小实现 - extreme 分享
  • 内网穿透的应用-Navidrome与cpolar本地搭建跨网络访问的云音乐服务器
  • 金融量化指标--2Alpha 阿尔法
  • Qoder 完整使用指南
  • Coze源码分析-资源库-删除插件-后端源码-数据访问和基础设施层
  • GitHub OAuth 登录实现
  • 容器-资源隔离机制
  • WGAI项目前后端项目简介及首页监控
  • 前端通过后端给的webrtc的链接,在前端展示,并更新实时状态