C# StringBuilder源码分析 📅 发布时间:2026/7/8 23:57:24 👁️ 浏览次数: 在 .NET 中StringBuilder是一个用于高效构建字符串的重要类。它通过避免频繁创建新字符串对象从而优化了性能。但其背后的实现机制却并不简单。一、核心字段与属性解析StringBuilder内部使用了字符数组char[]来存储字符串数据并通过链表的方式管理多个“块”Chunk以提升拼接效率。主要字段internalchar[]m_ChunkChars;// 当前块的字符数组internalintm_ChunkLength;// 当前块中已使用的字符数internalintm_ChunkOffset;// 当前块在整个字符串中的起始位置internalintm_MaxCapacity;// 最大容量默认为 int.MaxValueinternalconstintDefaultCapacity16;// 默认初始容量internalconstintMaxChunkSize8000;// 单个 Chunk 的最大长度Length 属性publicintLength{getm_ChunkOffsetm_ChunkLength;}表示当前整个字符串的总长度。二、构造函数分析1. 默认构造函数publicStringBuilder(){m_MaxCapacityint.MaxValue;m_ChunkCharsnewchar[DefaultCapacity];// 初始大小为16}默认分配一个长度为 16 的字符数组。2. 带字符串参数的构造函数publicStringBuilder(stringvalue,intstartIndex,intlength,intcapacity){...m_ChunkCharsGC.AllocateUninitializedArraychar(capacity);...}根据传入字符串的长度和指定容量选择较大的值作为初始容量避免多次扩容。3. 复制构造函数用于链表节点创建privateStringBuilder(StringBuilderfrom){m_ChunkLengthfrom.m_ChunkLength;m_ChunkOffsetfrom.m_ChunkOffset;m_ChunkCharsfrom.m_ChunkChars;m_ChunkPreviousfrom.m_ChunkPrevious;...}这个构造函数用于创建新的 Chunk 节点是链表结构的关键。三、Append 方法的工作原理以Append(char value, int repeatCount)为例来看StringBuilder如何处理追加操作publicStringBuilderAppend(charvalue,intrepeatCount){//省略边界检查代码intindexm_ChunkLength;while(repeatCount0){if(indexm_ChunkChars.Length){m_ChunkChars[index]value;--repeatCount;}else{m_ChunkLengthindex;ExpandByABlock(repeatCount);// 扩容并创建新 ChunkDebug.Assert(m_ChunkLength0);index0;}}m_ChunkLengthindex;returnthis;}核心逻辑如果当前字符数组还有空间则直接插入字符。如果空间不足调用ExpandByABlock()创建新 Chunk并将其链接到当前 Chunk 的前面。四、ExpandByABlock 方法详解该方法负责创建新的 Chunk 并更新当前 Chunk 的状态privatevoidExpandByABlock(intminBlockCharCount){intnewBlockLengthMath.Max(minBlockCharCount,Math.Min(Length,MaxChunkSize));char[]chunkCharsGC.AllocateUninitializedArraychar(newBlockLength);m_ChunkPreviousnewStringBuilder(this);// 创建前驱节点m_ChunkOffsetm_ChunkLength;m_ChunkLength0;m_ChunkCharschunkChars;}关键步骤计算新 Chunk 的大小不超过MaxChunkSize默认 8000也不小于所需字符数。分配新内存。创建前驱节点将当前 Chunk 封装成一个新的StringBuilder实例并赋值给m_ChunkPrevious。更新偏移量和长度当前 Chunk 清空准备写入新数据。切换字符数组将新分配的数组设为当前 Chunk 使用。五、图解1、初始状态默认构造函数创建 StringBuildervarsbnewStringBuilder();内部结构sb(current)│ ├── m_ChunkChars:[_ _ _ _ _ _ _ _ _ _ _ _ _ ___]← 默认大小为16├── m_ChunkLength:0├── m_ChunkOffset:0└── m_ChunkPrevious:null此时没有数据只是一个空的字符数组。2、第一次 Append 操作添加 “HELLO”sb.Append(HELLO);内部结构sb(current)│ ├── m_ChunkChars:[H E L L O _ _ _ _ _ _ _ _ ___]├── m_ChunkLength:5├── m_ChunkOffset:0└── m_ChunkPrevious:null此时字符数组还有 11 个空间可用。3、继续 Append添加 “WORLD”sb.Append(WORLD);字符串长度为 5刚好可以放入剩余空间中。内部结构sb(current)│ ├── m_ChunkChars:[H E L L O W O R L D _ _ _ _ ___]├── m_ChunkLength:10├── m_ChunkOffset:0└── m_ChunkPrevious:null此时字符数组还剩 6 个空位。4、扩容触发再次 Append 超出当前容量sb.Append(THIS_IS_A_LONG_STRING);// 长度超过剩余空间此时字符数组只剩 6 个位置但要插入的字符串长度为 20因此需要扩容。步骤解析更新当前 Chunk 的状态将m_ChunkLength设置为当前已使用的长度10。记录当前 Chunk 的偏移量为m_ChunkOffset m_ChunkLength 0 10 10。清空当前 Chunk 的使用长度设为 0。创建新 Chunk 并设置为前驱节点创建一个新的StringBuilder实例其字符数组大小根据所需内容和最大块大小8000决定。将当前 Chunk 的引用赋值给新 Chunk 的m_ChunkPrevious。切换当前 Chunk 到新分配的数组。扩容后结构newChunk(current)│ ├── m_ChunkChars:[T H I S _ I S _ A _ L O N G _ S T R I N G...]假设新块大小为32 ├── m_ChunkLength:20├── m_ChunkOffset:10└── m_ChunkPrevious:↓ oldChunk │ ├── m_ChunkChars:[H E L L O W O R L D _ _ _ _ ___]├── m_ChunkLength:10├── m_ChunkOffset:0└── m_ChunkPrevious:null注意此时sb变量指向的是newChunk也就是最后一个 Chunk。5、继续 Append 更多内容每次扩容都会在链表头部插入新的 Chunk并保留对旧 Chunk 的引用。最终形成一个“逆向链表”结构sb(current)→ newChunk3 → newChunk2 → oldChunk →null每个 Chunk 包含一部分字符串内容且可以通过m_ChunkPrevious向前追溯完整字符串。六、为什么使用逆向链表每个StringBuilder对象维护一个指向“前一个节点”的引用 (m_ChunkPrevious)而不是常见的“后一个节点”。这样做的好处尾部追加操作更高效由于用户总是从最后一个 Chunk 添加数据采用“逆向链表”可以快速定位到最后一个节点无需遍历整个链表。时间复杂度为 O(1)每次添加新 Chunk 都是在当前节点的基础上创建前驱节点无需查找最后一个节点。相比之下如果使用正向链表每个节点保存下一个节点引用则每次添加都需要遍历到末尾时间复杂度为 O(n)性能下降明显。七、链表结构带来的代价虽然链表提升了追加效率但也带来了一些缺点无法随机访问不能像数组一样直接通过索引访问某个字符。读取效率较低若需要从中间或开头插入数据需遍历整个链表效率不如单一数组。因此StringBuilder更适合尾部拼接的场景而不适合频繁的随机修改。八、最常用的Append方法StringBuilder.Append(string value)是最常用的方法Append(string? value)方法/// summary/// 将一个字符串追加到当前 StringBuilder 的末尾。/// /summary/// param namevalue要追加的字符串可以为 null。/param/// returns当前的 StringBuilder 实例以支持链式调用。/returnspublicStringBuilderAppend(string?value){// 如果传入的字符串为 null则直接返回不进行任何操作if(value!null){// 获取当前 chunk 的字符数组和已使用的长度char[]chunkCharsm_ChunkChars;intchunkLengthm_ChunkLength;intvalueLenvalue.Length;// 判断当前 chunk 是否还有足够的空间容纳要追加的字符串// 使用 uint 类型进行加法溢出检查确保不会超出数组长度if(((uint)chunkLength(uint)valueLen)(uint)chunkChars.Length){// 如果要追加的字符串长度较小最多两个字符则直接逐个字符复制if(valueLen2){if(valueLen0){// 追加第一个字符chunkChars[chunkLength]value[0];}if(valueLen1){// 追加第二个字符chunkChars[chunkLength1]value[1];}}else{// 如果要追加的字符串较长使用高效的固定指针方式复制字符unsafe{// 固定字符串和当前 chunk 的内存地址fixed(char*valuePtrvalue)fixed(char*destPtrchunkChars[chunkLength]){// 使用内部高效复制方法将字符复制到当前 chunk 中string.wstrcpy(destPtr,valuePtr,valueLen);}}}// 更新当前 chunk 已使用的字符数m_ChunkLengthchunkLengthvalueLen;}else{// 当前 chunk 空间不足调用 AppendHelper 方法进行扩容和追加AppendHelper(value);}}returnthis;}AppendHelper(string value)方法/// summary/// 用于处理当前 chunk 空间不足时的追加操作。/// 会分配新的 chunk 并将字符串内容复制进去。/// /summary/// param namevalue要追加的字符串非 null。/paramprivatevoidAppendHelper(stringvalue){unsafe{// 固定字符串的内存地址以便进行指针操作fixed(char*valueCharsvalue){// 调用 Append(char*, int) 方法进行实际的追加操作Append(valueChars,value.Length);}}}Append(char* value, int valueCount)方法/// summary/// 将一个字符指针指向的缓冲区内容追加到当前 StringBuilder 的末尾。/// /summary/// param namevalue指向要追加的字符缓冲区的指针。/param/// param namevalueCount要追加的字符数量。/param/// returns当前的 StringBuilder 实例以支持链式调用。/returns/// exception crefArgumentOutOfRangeException当 valueCount 为负数或超过最大容量时抛出。/exception[CLSCompliant(false)]publicunsafeStringBuilderAppend(char*value,intvalueCount){// 不检查 value 是否为 null因为访问 null 指针会自动抛出 NullReferenceException// 检查 valueCount 是否为负数防止非法参数if(valueCount0){thrownewArgumentOutOfRangeException(nameof(valueCount),SR.ArgumentOutOfRange_NegativeCount);}// 计算新字符串的总长度当前长度 要追加的字符数intnewLengthLengthvalueCount;// 检查是否超过最大容量m_MaxCapacity或发生整数溢出if(newLengthm_MaxCapacity||newLengthvalueCount){thrownewArgumentOutOfRangeException(nameof(valueCount),SR.ArgumentOutOfRange_LengthGreaterThanCapacity);}// 尝试将数据直接追加到当前 chunk 的剩余空间中intnewIndexvalueCountm_ChunkLength;// 如果当前 chunk 有足够的空间容纳所有数据直接复制if(newIndexm_ChunkChars.Length){ThreadSafeCopy(value,m_ChunkChars,m_ChunkLength,valueCount);m_ChunkLengthnewIndex;// 更新当前 chunk 使用的字符数}else{// 当前 chunk 空间不足只能先复制一部分// 计算当前 chunk 中剩余可用空间intfirstLengthm_ChunkChars.Length-m_ChunkLength;if(firstLength0){// 复制第一部分到当前 chunkThreadSafeCopy(value,m_ChunkChars,m_ChunkLength,firstLength);m_ChunkLengthm_ChunkChars.Length;// 当前 chunk 已满}// 剩余要复制的字符数intrestLengthvalueCount-firstLength;// 扩展 StringBuilder添加一个新的 chunkExpandByABlock(restLength);Debug.Assert(m_ChunkLength0,添加新 chunk 后chunk 的长度应为 0表示空块);// 将剩余部分复制到新的 chunk 中ThreadSafeCopy(valuefirstLength,m_ChunkChars,0,restLength);m_ChunkLengthrestLength;// 更新新 chunk 的使用长度}// 验证内部状态是否一致调试用AssertInvariants();returnthis;}ExpandByABlock(int minBlockCharCount)方法/// summary/// 将当前 chunk 的字符转移到一个新的 chunk 中并为当前 chunk 分配一个新的字符缓冲区。/// 这是扩容机制的一部分用于支持 StringBuilder 的动态增长。/// /summary/// param nameminBlockCharCount新 chunk 缓冲区的最小容量。/param/// remarks/// - 此方法要求当前 chunk 已满即 m_ChunkLength m_ChunkChars.Length。/// - 假设当前 chunk 是链表中的最后一个 chunk。/// /remarksprivatevoidExpandByABlock(intminBlockCharCount){// 调试断言确保当前 chunk 已满容量 长度Debug.Assert(CapacityLength,nameof(ExpandByABlock) should only be called when there is no space left.);// 调试断言确保请求的最小容量是正数Debug.Assert(minBlockCharCount0);// 验证当前 chunk 的状态是否合法调试用AssertInvariants();// 检查扩容后是否会超过最大容量或发生整数溢出if((minBlockCharCountLength)m_MaxCapacity||minBlockCharCountLengthminBlockCharCount){thrownewArgumentOutOfRangeException(requiredLength,SR.ArgumentOutOfRange_SmallCapacity);}// 计算新 chunk 的容量// - 至少为 minBlockCharCount满足当前需求// - 如果当前字符串长度较小则取当前长度实现“容量翻倍”策略// - 但不能超过 MaxChunkSize防止分配过大内存块intnewBlockLengthMath.Max(minBlockCharCount,Math.Min(Length,MaxChunkSize));// 检查是否会溢出 int.MaxValue逻辑总长度if(m_ChunkOffsetm_ChunkLengthnewBlockLengthnewBlockLength){thrownewOutOfMemoryException();}// 提前分配新的字符数组避免在分配失败时留下不一致的状态char[]chunkCharsGC.AllocateUninitializedArraychar(newBlockLength);// 创建一个新的 StringBuilder 实例新 chunk并将当前 chunk 设置为其前一个 chunkm_ChunkPreviousnewStringBuilder(this);// 更新当前 chunk 的偏移量当前 chunk 的字符总数 前面所有 chunk 的字符总数m_ChunkOffsetm_ChunkLength;// 当前 chunk 已经“转移”了所有字符因此长度清零m_ChunkLength0;// 将当前 chunk 的字符数组指向新分配的缓冲区m_ChunkCharschunkChars;// 再次验证当前 chunk 的状态是否合法调试用AssertInvariants();}ExpandByABlock方法是StringBuilder扩容机制的核心部分之一原理和Append(char value, int repeatCount)类似它会创建一个新的 chunk并将当前 chunk 的数据“转移”过去。然后将当前 chunk清空指向一个新分配的缓冲区准备接收新数据。通过这种方式StringBuilder可以高效地链式增长而不会频繁复制整个字符串。九、ToString方法详解publicoverridestringToString(){// 断言检查确保当前 StringBuilder 的内部状态是合法的调试时使用AssertInvariants();// 如果总长度为 0直接返回空字符串避免不必要的操作if(Length0){returnstring.Empty;}// 快速分配一个指定长度的字符串用于最终结果// string.FastAllocateString 是内部方法用于高效分配字符串空间stringresultstring.FastAllocateString(Length);// 使用 unsafe 代码块允许使用指针进行高效内存操作unsafe{// 将结果字符串的指针固定防止在 GC 中被移动否则指针会失效fixed(char*destinationPtrresult){// 从当前 chunk 开始沿着链表向前遍历所有 chunksStringBuilder?chunkthis;// 循环处理每一个 chunkdo{// 只有当前 chunk 中有数据才进行复制if(chunk.m_ChunkLength0){// 将 chunk 中的字段复制到局部变量中// 这样可以避免在多线程环境下字段被修改导致的数据不一致问题char[]sourceArraychunk.m_ChunkChars;intchunkOffsetchunk.m_ChunkOffset;// 当前 chunk 在整个字符串中的起始位置intchunkLengthchunk.m_ChunkLength;// 当前 chunk 中实际存储的字符数// 边界检查确保不会越界访问源数组和目标字符串if((uint)(chunkLengthchunkOffset)(uint)result.Length(uint)chunkLength(uint)sourceArray.Length){// 将源字符数组的指针固定fixed(char*sourcePtrsourceArray[0]){// 将当前 chunk 的字符复制到最终字符串的对应位置// destinationPtr chunkOffset 表示目标字符串中的偏移位置// sourcePtr 是源字符数组的起始地址// chunkLength 是要复制的字符数量string.wstrcpy(destinationPtrchunkOffset,sourcePtr,chunkLength);}}else{// 如果越界抛出异常thrownewArgumentOutOfRangeException(nameof(chunkLength),SR.ArgumentOutOfRange_Index);}}// 移动到前一个 chunk继续处理链表中的其他部分chunkchunk.m_ChunkPrevious;}while(chunk!null);// 直到没有更多 chunk 为止// 返回最终拼接好的字符串returnresult;}}}当调用sb.ToString()时会从最后一个 Chunk 开始沿着m_ChunkPrevious一路向前遍历把所有 Chunk 的字符数组拼接起来最终返回完整的字符串。这个过程虽然比单一块慢但比起频繁复制数组在大多数场景下是值得的。
windows系统安装mqtt 前言 最近在关注物联网相关的信息,了解到MQTT协议。记录一下windows安装MQTT。 在 Windows 10 系统下安装 MQTT,一般需要先安装 MQTT 代理服务器(以 Mosquitto 为例),再安装 MQTT 客户端工具。以下是具体的安装步骤&am… 2026/7/8 6:39:40
Springboot+vue高考志愿填报系统的设计与实现 文章目录前言详细视频演示具体实现截图后端框架SpringBoot前端框架Vue持久层框架MyBaits成功系统案例:参考代码数据库源码获取前言 博主介绍:CSDN特邀作者、985高校计算机专业毕业、现任某互联网大厂高级全栈开发工程师、Gitee/掘金/华为云/阿里云/GitHub等平台持续… 2026/5/17 12:39:46
电气设计-Eplan 1.Eplan简介Eplan是电气设计比较常用的绘图工具,对比CAD而言它的设计更加方便简单快捷,内容功能也很强大,同时大部分工控行业的供应商也针对Eplan的功能也做出了自己的产品组件edz文件,包含了产品的安装图、接线原理图、尺寸以及说… 2026/7/8 22:38:53
终极Adobe Illustrator脚本工具箱:20+插件快速提升设计效率300% 终极Adobe Illustrator脚本工具箱:20插件快速提升设计效率300% 【免费下载链接】illustrator-scripts Adobe Illustrator scripts 项目地址: https://gitcode.com/gh_mirrors/il/illustrator-scripts Adobe Illustrator脚本集合是一个专门为设计师打造的强大… 2026/7/8 23:57:01
Halcon OCV 24.11 多字符核验优化:1个模型输出5个字符分数,效率提升80% Halcon OCV 24.11 多字符核验工业级优化:单模型批量处理实战在工业视觉检测领域,字符验证(OCV)一直是产线质量控制的关键环节。传统方案要求每个待检字符单独训练模型,当面对序列号、日期码等多字符场景时,… 2026/7/8 23:57:01
【JAVA毕设源码分享】基于SpringBoot的课堂考勤系统设计与实现(程序+文档+代码讲解+一条龙定制) 博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am… 2026/7/8 23:55:01
OpenCV 4.8 直方图均衡化实战:3种方法处理低对比度医学图像 OpenCV 4.8 直方图均衡化实战:3种方法处理低对比度医学图像医学影像诊断中,约30%的误诊源于图像对比度不足导致的细节丢失。本文将深入探讨如何利用OpenCV 4.8的直方图均衡化技术,通过三种不同方法显著提升X光、CT等医学图像的诊断价值。我们… 2026/7/8 23:53:00
线激光扫描三维成像:5步搭建工业级检测系统(含相机/激光器选型) 线激光扫描三维成像:5步搭建工业级检测系统(含相机/激光器选型)在工业4.0时代,三维成像技术正成为智能制造的核心支撑。当传统二维视觉无法满足复杂曲面、精密尺寸的检测需求时,线激光扫描技术凭借其微米级精度和高速采… 2026/7/8 23:51:00
YOLOv5 OpenVINO 部署瓶颈分析:Numpy 转 Tensor 耗时 9.5ms 的 3 种优化策略 YOLOv5 OpenVINO 部署性能优化:从数据预处理到量化加速的完整指南在计算机视觉领域,实时目标检测系统的性能优化一直是开发者关注的重点。当我们将YOLOv5模型部署到Intel平台使用OpenVINO运行时,往往会遇到一个关键瓶颈——数据预处理阶段&am… 2026/7/8 23:48:59
BetterNCM安装器:高效管理网易云插件的最佳选择 BetterNCM安装器:高效管理网易云插件的最佳选择 【免费下载链接】BetterNCM-Installer 一键安装 Better 系软件 项目地址: https://gitcode.com/gh_mirrors/be/BetterNCM-Installer 还在为网易云音乐插件的繁琐安装流程而烦恼吗?BetterNCM安装器是… 2026/7/8 0:02:48
运动控制系统安全设置对比:ECI3808的3种限位保护与急停逻辑实现 运动控制系统安全机制深度解析:限位保护与急停逻辑的设计哲学在精密制造与自动化领域,运动控制系统的安全设计绝非简单的功能堆砌,而是一套融合了机械工程、电气原理和软件算法的防御体系。当一台数控机床以每分钟数万转的速度运转࿰… 2026/7/8 0:06:48
AI大模型应用开发:小白也能抓住的红利风口,收藏这篇入门指南! 文章指出,虽然微软等科技巨头在裁员,但英伟达等公司却在积极扩招AI相关人才,尤其是具身智能、仿真等领域。AI行业正在经历结构性调整,传统岗位被淘汰,而大模型应用开发等新岗位需求旺盛。对于想转行或学习AI的普通人来… 2026/7/8 0:10:49
6个月转型AI工程师:实战路径与核心技能 1. 项目概述:6个月转型AI工程师的可行性路径在2023年大模型技术爆发的背景下,AI工程师岗位需求同比增长217%(LinkedIn数据)。不同于传统算法工程师需要3-5年培养周期,现代AI工程师更侧重工程化落地能力。我在硅谷科技公… 2026/7/7 11:26:57
TPAFE0808与PIC18F87K22的多通道信号采集方案 1. 项目背景与核心需求在工业自动化、医疗设备和科研仪器等领域,多通道信号采集与系统监测是基础且关键的技术需求。传统方案往往面临通道数量不足、信号调理复杂、系统集成度低等问题。TPAFE0808作为一款8通道模拟前端芯片,与PIC18F87K22微控制器的组合… 2026/7/8 20:15:17
STC3115与PIC18LF26K80构建高精度电池管理系统 1. STC3115与PIC18LF26K80在电池管理系统中的核心价值在现代电子设备中,电池管理系统(BMS)的重要性不亚于设备的核心处理器。STC3115作为一款高精度电池电量监测IC,与PIC18LF26K80微控制器的组合,构成了一个既能精确监控又能智能管理的完整解… 2026/7/8 14:25:08