当前位置:首页 > 文章列表 > Golang > Go教程 > Go strings.Builder 怎样估算容量后构造长字符串完成一个可复用的最小示例

Go strings.Builder 怎样估算容量后构造长字符串完成一个可复用的最小示例

来源:17golang原创 2026-11-07 02:14:32 0浏览 收藏

用 strings.Builder 构造长字符串时,可以先统计所有片段及分隔符的字节数,在写入前调用一次 Grow。关键不是把容量猜得越大越好,而是按实际输出规则计算,设置业务上限,并让 Builder 保持局部使用。下面用可复用的 Compose 函数完成这件事。

Go 官方地址:https://go.dev/

我会在输出结构已经明确、片段可以重复遍历时考虑这种写法。若只是固定分隔符拼接,strings.Join 往往更简单;需要加入字节预算或后续自定义输出规则时,单独封装 Builder 才更有价值。这里给出设计取舍和可复现示例,不虚构性能测量或线上经历。

先判断问题是内容错误还是容量策略

如果输出缺字符或分隔符重复,先修正写入规则,Grow 不负责修复内容。若输出正确、但希望减少扩容,再看是否能提前知道总字节数。性能收益仍需按真实负载测量,不能仅凭用了 Builder 就认定更快。

我会先列出三个条件:输出是否必须整段保存在内存里,片段是否可以稳定遍历两次,允许的最大输出是多少。流式内容或大到不宜驻留内存的响应,更适合直接写入 io.Writer,而不是先构造一个更大的字符串。

我会先把预算写成字节公式

本例的输出由 parts 和 sep 组成:各片段长度相加,再加上相邻片段之间的分隔符。这里使用 len(string) 的字节数,不是可见字符数。对示例中的“山”和“云”,容量应按各自的 UTF-8 字节计算,不能都算成1。

字符串片段分隔符与Builder字节预算的静态关系
静态结构说明图:total 来自片段和分隔符的字节长度,并受 maxBytes 约束;Grow 接收已经确定的预算。

我没有直接把片段数量和分隔符长度做乘法,而是逐次累加并检查 maxBytes。这样写略长,但能让每次增加的预算都经过同一入口。判断使用 n > maxBytes-total,避免先做可能溢出的加法。

把构造器留在函数内部

完整示例如下。Compose 保留输入顺序,空片段仍占一个位置,因此分隔符不会因为片段为空而自动消失。预算失败返回可识别的 ErrTooLarge,成功时只返回字符串。

package main

import (
    "errors"
    "fmt"
    "os"
    "strings"
)

var ErrTooLarge = errors.New("输出超过字节预算")

// Compose 以字节为单位计算准确大小;调用期间 parts 不应被并发修改。
func Compose(parts []string, sep string, maxBytes int) (string, error) {
    if maxBytes  maxBytes-total {
            return ErrTooLarge
        }
        total += n
        return nil
    }
    for i, part := range parts {
        if i > 0 {
            if err := add(len(sep)); err != nil {
                return "", fmt.Errorf("分隔符 %d: %w", i, err)
            }
        }
        if err := add(len(part)); err != nil {
            return "", fmt.Errorf("片段 %d: %w", i, err)
        }
    }
    // Builder 保持局部使用,不按值复制,也不跨协程共享。
    var b strings.Builder
    b.Grow(total)
    for i, part := range parts {
        if i > 0 {
            b.WriteString(sep)
        }
        b.WriteString(part)
    }
    return b.String(), nil
}

func run() error {
    parts := []string{"山", "river", "云"}
    text, err := Compose(parts, " | ", 1024)
    if err != nil {
        return err
    }
    fmt.Printf("%s\nbytes=%d\n", text, len(text))

    // 预算不足时函数拒绝构造,不用 recover 吞掉 Grow 的问题。
    _, err = Compose(parts, " | ", 8)
    fmt.Printf("tooLarge=%t\n", errors.Is(err, ErrTooLarge))
    return nil
}

func main() {
    if err := run(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}
Compose与局部Builder及返回结果的静态结构
静态结构说明图:Compose 持有局部 Builder,通过写入方法构造并返回字符串;预算错误与成功结果分别处理。

这里对 WriteString 的返回值没有单独分支,因为该具体方法的错误返回为 nil;这不能推广到任意 io.Writer。函数也不返回 Builder,不把已经使用的构造器赋值给另一份副本。

保存为 main.go 后,在独立示例目录运行:

# 新示例目录尚未初始化模块时执行一次。
go mod init example.com/builderbudget
# 运行本例,观察文本、字节长度与预算拒绝分支。
go run .

按给定输入,预期文本为 山 | river | 云,字节数为17;预算8会命中 ErrTooLarge。空切片且预算非负时,预期返回空字符串和 nil 错误。以上是代码预期行为,供读者复现对照,不是本机运行日志。

Grow 应该预留多少,已有内容又怎么算

官方契约中的 Grow(n) 表示为后续再写入 n 个字节预留空间,不是把总容量设为 n,也不会把当前长度增加 n。本例在空 Builder 上调用,total 正好就是后续要写入的字节量。

若先写了前缀,再计算最终大小,传入的应是还要追加的字节预算,而不是不加区分地再传一遍总长度。容量可能大于请求值,代码不要断言 Cap 必须与 total 完全相等。

现象优先判断处理方式
中文容量估得偏小是否把字符数当成字节数按实际写入字符串的 len 统计
调用 Grow 后 Len 没变是否混淆容量与内容长度继续写入,Grow 本身不生成内容
预算乘法变成异常值是否先算大整数再检查在每次累加前检查剩余预算
片段很短却预留巨大容量估算是否过度保守使用准确长度或取消预留
构造过程中发生复制问题是否按值复制已使用的 Builder保持局部所有权,必要时传指针

需要回退时,我会保留业务预算

若要对比不预留的实现,可以只去掉 b.Grow(total),保留前面的长度计算与上限检查。这样输出规则和预算策略不变,改变的仅是预留容量。不要为了回退性能优化,把限制输出大小的业务约束也一并删除。

准确预估需要遍历两次片段:一次计数,一次写入。若来源只能消费一次,不能直接套用该函数。也不要在两次遍历之间并发修改 parts 的元素,否则计数与输出可能不一致。

maxBytes 限制的是本函数计划生成的内容字节数,不是整个进程的内存上限。分配仍需要足够内存,返回字符串的生命周期也会影响资源占用。我会根据调用场景选择合理预算,而不把“没有整数溢出”误当成“内存肯定够用”。

常见问题

估小了会截断吗?Grow 不设置输出长度上限,后续写入仍可扩容。本例的上限由预先计算并拒绝超预算来实现。

负数能传给 Grow 吗?不能,负数会触发 panic。不要用 recover 代替预算计算;本例先拒绝负的 maxBytes,再保持 total 为非负数。

可以在多个请求之间共享 Builder 吗?本例不共享。每次调用创建自己的构造器,所有权简单,也避免多个请求同时修改同一内容。

何时直接选 strings.Join?只有片段与固定分隔符、没有额外构造规则时,我会优先用 Join。要加预算,也可先计算并检查长度,再交给 Join,不必为了展示 Builder 而增加维护成本。

官方参考:strings.Builder 与 Grow 契约;Go 官方 Builder 实现。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go bytes.Buffer 怎样复用缓冲区并避免旧内容残留处理空输入并保留错误边界Go bytes.Buffer 怎样复用缓冲区并避免旧内容残留处理空输入并保留错误边界
上一篇
Go bytes.Buffer 怎样复用缓冲区并避免旧内容残留处理空输入并保留错误边界
Go bytes.Buffer 怎样复用缓冲区并避免旧内容残留在批处理代码中减少额外分配
下一篇
Go bytes.Buffer 怎样复用缓冲区并避免旧内容残留在批处理代码中减少额外分配
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    919次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    924次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1022次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1000次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    827次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码