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

Go 的 strings.Builder 实战:拼接性能、Grow 预分配与何时该用 bytes.Buffer

Go 的 strings.Builder 实战:拼接性能、Grow 预分配与何时该用 bytes.Buffer

Go 里拼字符串,新手第一反应是+=,循环几万次后 CPU 和内存双双爆表。老手会甩一句「用 strings.Builder」,但很少讲清楚它为什么快、Grow 有什么用、什么时候反而该用 bytes.Buffer。这篇用能跑的代码说明白。

为什么 += 在循环里是灾难

Go 的 string 是不可变的。s += x每次都要新分配一块内存,把旧内容和新内容拷过去,旧的等着被 GC。循环 N 次就是 O(N²) 的拷贝量:

// 反例:每次 += 都重新分配 + 拷贝,循环越长越慢funcjoinBad(parts[]string)string{s:=""for_,p:=rangeparts{s+=p// 每轮生成一个新 string}returns}

一万个短字符串就能让它比正确写法慢几十倍,而且制造大量临时垃圾。

strings.Builder:一块可扩展的缓冲区

strings.Builder内部维护一个[]byte,WriteString直接往后追加,只在容量不够时才扩容,最后String()零拷贝地把 buffer 转成 string:

funcjoinGood(parts[]string)string{varb strings.Builderfor_,p:=rangeparts{b.WriteString(p)// 追加到内部 []byte,不重新分配整串}returnb.String()// 底层数组直接转 string,不拷贝}

String()之所以能零拷贝,是因为 Builder 用了unsafe[]byte直接转成 string,并且禁止拷贝自身(拷贝会触发 panic,防止两个 Builder 共享同一底层数组)。所以别值传递 Builder,要传就传指针。

Grow:提前知道总长度就一次性分配

即便有 Builder,如果不预分配,它仍会在扩容时多次 realloc。当你能估算最终长度时,用Grow(n)一次性把容量备好,彻底消灭中途扩容:

funcjoinFast(parts[]string)string{total:=0for_,p:=rangeparts{total+=len(p)}varb strings.Builder b.Grow(total)// 一次分配到位,后续 Write 不再扩容for_,p:=rangeparts{b.WriteString(p)}returnb.String()}

go test -bench实测,joinFast通常比不 Grow 的版本少一半以上的内存分配次数(allocs/op)。拼接大量数据时,Grow 是性价比最高的一行优化。

strings.Builder vs bytes.Buffer 怎么选

两者都是可扩展缓冲区,区别在用途:

  • 最终要 string,而且只往里写strings.Builder。它的String()零拷贝,更省。
  • 需要读回来(实现 io.Reader)、或要反复 Reset 复用、或最终要 []bytebytes.Buffer。Builder 没有读接口,Bytes()也没有。
// bytes.Buffer 能当 io.Reader/Writer 用,还能 Reset 复用varbuf bytes.Buffer buf.WriteString("hello")io.Copy(os.Stdout,&buf)// Builder 做不到:它不是 Reader

一句话:纯拼字符串用 Builder,要参与 IO 流或需要读取用 Buffer。注意bytes.Buffer.String()是有拷贝的,别为了「拼个字符串」平白多一次拷贝。

一个常被忽略的点:Builder 也能 fmt

不想手动拼格式化字符串时,fmt.Fprintf可以直接往 Builder 写,避免生成中间 string:

varb strings.Builderfori,p:=rangeparts{fmt.Fprintf(&b,"%d:%s;",i,p)// 直接写进 Builder,无中间字符串}

小结

  • 循环里+=是 O(N²) 拷贝,数据量一大就崩;拼字符串默认上strings.Builder
  • String()零拷贝,但 Builder禁止值拷贝,传参用指针。
  • 能估算长度就Grow(total)一次分配到位,实测能砍掉一半以上的allocs/op
  • 选型记忆点:只写、最终要 string 用 Builder;要读回来/复用/输出 []byte 用 bytes.Buffer。
http://www.jsqmd.com/news/1389759/

相关文章:

  • 4小时指挥一个AI动画工厂:从风格圣经到定时任务的完整拆解
  • 抖店订单消息自动提醒
  • 产业互联网大厂Java面试实录:JDK17、Redis分布式锁、Kafka订单削峰、Seata分布式事务、Spring AI+RAG智能供应链助手,谢飞机三轮被虐哭(附完整答案解析)
  • 解决方案:AD、Mutisim等设计软件卡在加载页面无法打开
  • Ring-2.6-1T性能评测:87.60分PinchBench成绩背后的技术实力
  • C++-DLL开发环境搭建
  • 数学建模竞赛实战指南:从解题思路到完整论文的系统方法论
  • vue3动态路由详细效果
  • Windows Elevation项目最佳实践:提升系统安全性的漏洞修复策略
  • 数学建模竞赛实战指南:从问题抽象到论文写作的全流程解析
  • DyberPet 桌面宠物框架:如何用 30 分钟让喜欢的角色住进你的电脑桌面?
  • 2分钟零配置上手Tesseract.js:纯前端图片文字识别完整指南
  • Coding Agent的墙式散文:为什么我们需要它用眼睛能看懂的方式说话
  • 辽宁建设培训网站 如何助力从业者职业晋升与技能提升指南
  • APMCM数学建模竞赛备赛指南:从赛题解析到实战策略
  • PHP中的Xdebug是什么?如何使用它进行调试?
  • 单人数学建模竞赛全攻略:从可行性分析到72小时极限生存指南
  • 买车别只看指导价:算清这4项隐性开销,才是真正的用车成本
  • 电铸工艺?微米级孔径筛网?
  • 数学建模竞赛实战指南:从模型构建到论文撰写的全流程解析
  • 网站建设报价模块揭秘:为何你的企业官网预算总是失控?
  • Docker Compose镜像整体导出与还原:离线环境迁移完整指南
  • 设计师必备:高效下载国外设计资源的实战指南与工具链
  • 走难而正确的路,美尔凯特为何会走向厨房空调领域?
  • APMCM数学建模竞赛全攻略:从邀请函解读到论文提交的实战指南
  • Dism++系统优化完全指南:免费Windows清理工具从入门到精通的保姆级教程
  • 麦芽AI vs workbuddy / Codex(四):对话/分析/全自动三模式,把「做到什么程度」的决策权还给用户
  • 2026年地板打蜡服务实力之选:木地板、石材、家具深层养护专业品牌机构解析 - 卓企推荐
  • 2024年传统网站建设公司的出路在哪里?深耕细分领域与提升服务价值的实战指南
  • 彻底解决vcruntime140_1.dll缺失:从原理到修复的完整指南