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://www.pswp.cn/web/96116.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/96116.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LangChain实战(十七):构建与PDF/PPT文档对话的AI助手

本文是《LangChain实战课》系列的第十七篇,将专篇深入讲解如何构建能够与PDF和PPT文档进行智能对话的AI助手。通过学习本文,您将掌握复杂格式文档的解析技巧、文本与表格处理技术,以及实现精准问答的系统方法。 前言 在日常工作和学习中,PDF和PPT文档是我们最常接触的文档…

鱼眼相机模型

鱼眼相机模型 最近涉及鱼眼相机模型、标定使用等&#xff0c;作为记录&#xff0c;更新很久不曾更新的博客。 文章目录鱼眼相机模型1 相机成像2 鱼眼模型3 畸变3.1 适用针孔和MEI3.2 Kannala-Brandt鱼眼模型4 代码实现1 相机成像 针孔相机&#xff1a;所有光线从一个孔&#xf…

大语言模型提示词工程详尽实战指南

引言&#xff1a;与大型语言模型&#xff08;LLM&#xff09;高效对话的艺术大型语言模型&#xff08;LLM&#xff09;——例如我们熟知的GPT系列、Claude、Llama等——在自然语言处理&#xff08;NLP&#xff09;领域展现了惊人的能力&#xff0c;能够执行文本摘要、翻译、代码…

HTTP 请求体格式详解

1. 概览与概念 Content-Type&#xff1a;HTTP 请求/响应头&#xff0c;表示消息体的媒体类型&#xff08;MIME type&#xff09;。服务端用它决定如何解析请求体。常见场景&#xff1a; 纯结构化数据&#xff08;JSON&#xff09; → application/json表单 文件上传 → multip…

事务设置和消息分发

事务 RabbitMQ是基于AMQP协议实现的&#xff0c;该协议实现了事务机制&#xff0c;因此RabbitMQ也支持事务机制. SpringAMQP也提供了对事务相关的操作&#xff0c;RabbitMQ事务允许开发者确保消息的发送和接收是原子性的&#xff0c;要么 全部成功&#xff0c;要么全部失败.| 前…

Python 中 try / except / else / finally 异常处理详解

1. 基本结构 try:# 可能会抛出异常的代码 except SomeException as e:# 捕获并处理异常 else:# 如果 try 中代码没有异常&#xff0c;就执行这里 finally:# 无论是否发生异常&#xff0c;最后都会执行这里2. 各部分的作用 try 用途&#xff1a;包含可能发生异常的代码段。如果代…

冰火岛 Tech 传:Apple Foundation Models 心法解密(下集)

引子 上集说到冰火岛冰屋内,谢逊、张翠山、殷素素三人亲见 “指令(Instructions)” 如何让 AI 脱胎换骨,从木讷报地名的 “愣头青”,变身为文采斐然的 “旅行作家”。 正当素素惊叹这 AI 武学的奇妙时,谢逊却突然神色一凛,指着手腕上用冰屑刻的 “4096” 字样道:“这等…

Qt信号与槽机制全面解析

✨ 1. 核心概念信号与槽是Qt独创的一种对象间通信机制&#xff0c;它使得一个对象的状态变化或事件发生能够自动通知其他对象作出响应&#xff0c;从而实现高度解耦的代码设计。1.1 信号&#xff08;Signals&#xff09;定义&#xff1a;信号是由对象在特定事件发生时发出&…

2025年COR SCI2区,基于近似细胞分解的能源高效无人机路径规划问题用于地质灾害监测,深度解析+性能实测

目录1.摘要2.问题描述与数学模型3.能源网格混合元启发式算法4.结果展示5.参考文献6.代码获取7.算法辅导应用定制读者交流1.摘要 本文提出了一种能源高效的无人机路径规划方法&#xff08;EURP&#xff09;用于监测分散的地质灾害易发区域&#xff0c;通过建立无人机飞行模式的…

LangChain关于提示词的几种写法

上文中 <---->(可点击移步查阅),我们已经了解到LangChain如何编写调用大模型的一个基本框架, 在编写完这个基本框架后,就要与大模型进行交互了,这时候,就需要我们编写提示词了, 那么以下,就会介绍提示词的几种编写方式 1 可携带变量的提示词 1.1 PromptTempla…

CyberPoC 是一个现代化的网络安全练习和竞赛平台,支持容器化部署的安全挑战,为用户提供实践网络安全技能的环境。

工具介绍 CyberPoC 是一个现代化的网络安全练习和竞赛平台&#xff0c;支持容器化部署的安全挑战&#xff0c;为用户提供实践网络安全技能的环境。 &#x1f680; 快速开始 使用 Docker Compose (推荐) 克隆项目 git clone https://github.com/dushixiang/cyberpoc.gitcd cy…

【单片机day03】

UART串口通用异步收发器&#xff0c;2个串口(1个串口被用于ISP下载程序&#xff0c;1个串口被用于和主机之间的通信)全双工&#xff08;两根线TX、RX&#xff09;、串行、异步通信方式&#xff1a;单工&#xff1a;发送方和接收方是固定的&#xff0c;数据只能从发送方 发给 …

react16到react19更新及底层实现是什么以及区别

好问题 &#x1f44d;。React 16 → 19 的更新跨度其实涵盖了 React 的几个“时代级”的演进&#xff1a;从 Fiber 架构 的重写、到 并发特性 的引入&#xff0c;再到 React Server Components 和 现代语法支持。我帮你梳理下主要更新点和底层实现的区别&#xff0c;方便你系统…

【分享】基于百度脑图,并使用Vue二次开发的用例脑图编辑器组件

偶然间发现了这个项目&#xff0c;分享出来。 【分享】基于百度脑图&#xff0c;并使用Vue二次开发的用例脑图编辑器组件1 项目地址2 项目简介3 项目部署3.1 安装node和npm3.2 项目下载3.3 修改npm镜像源3.4 部署4 项目中使用1 项目地址 基于百度脑图&#xff0c;并使用Vue二次…

Kotlin中抽象类和开放类

抽象类 (Abstract Class) 定义和特点 抽象类使用 abstract 关键字声明&#xff0c;是一种不能被直接实例化的特殊类&#xff0c;主要用于被其他类继承。 abstract class Base {open fun f() {} }abstract class Derived : Base() {override abstract fun f() // 抽象成员在类中…

TensorFlow深度学习实战(37)——深度学习的数学原理

TensorFlow深度学习实战&#xff08;37&#xff09;——深度学习的数学原理0. 前言1. 反向传播历史2. 微积分相关概念2.1 向量2.2 导数和梯度2.3 梯度下降2.4 链式法则2.5 常用求导公式2.6 矩阵运算3. 激活函数4. 反向传播4.1 前向计算4.2 反向传播5. 交叉熵及其导数6. 批量梯度…

1.1 汽车运行滚动阻力

汽车运行阻力由4部分构成&#xff1a;滚动阻力、空气阻力、坡度阻力、加速阻力。 1).汽车在水平道路上等速行驶时&#xff0c;必须克服来自地面的滚动阻力和来自空气的空气阻力。 2). 当汽车在坡道上上坡行驶时&#xff0c;还必须克服重力沿坡道的分力&#xff0c;称为坡度阻…

e203000

1&#xff09;①BIU作为核心通信枢纽&#xff0c;主要承担两大功能&#xff1a;一是连接处理器核内的关键执行单元&#xff08;包括IFU、LSU和EAI协处理器&#xff09;&#xff0c;统一管理指令和数据的内部传输路径&#xff1b;二是作为"核内计算"与"核外资源&…

Infortrend普安科技IEC私有云平台VM解决方案

Infortrend企业云&#xff08;IEC&#xff09;内置Hypervisor运行VM。功能完整、无需额外付费。在本文中&#xff0c;我们将为您详细介绍IEC是如何支持 VM的。市场现状与挑战市场现状 虚拟化市场面临转型&#xff0c;主流厂商&#xff08;如 VMware&#xff09;改用订阅制…

【代码随想录算法训练营——Day6(Day5周日休息)】哈希表——242.有效的字母异位词、349.两个数组的交集、202.快乐数、1.两数之和

LeetCode题目链接 https://leetcode.cn/problems/valid-anagram/ https://leetcode.cn/problems/intersection-of-two-arrays/ https://leetcode.cn/problems/happy-number/ https://leetcode.cn/problems/two-sum/ 题解 242.有效的字母异位词 这道题要想到用哈希表来做。同时注…