字符串拼接应优先使用strings.builder,因其预分配容量、写入不拷贝,性能比+和fmt.sprintf高3–10倍;子串提取用切片s[start:end]而非不存在的strings.substring;比较用==而非strings.equalfold或bytes.equal以避免开销;必要时用unsafe.string或bytes包减少分配。

字符串拼接用 strings.Builder,别用 + 或 fmt.Sprintf
Go 中字符串不可变,每次 + 都会分配新内存并复制旧内容,小量拼接看不出问题,循环里拼 1000 次就明显卡顿。fmt.Sprintf 还要解析格式串、做类型反射,开销更大。
- 用
strings.Builder:预分配容量 + 写入不拷贝,性能提升 3–10 倍(取决于长度) - 初始化时尽量传入预估容量:
var b strings.Builder; b.Grow(1024),避免多次扩容 - 不要在循环外复用
Builder后忘记b.Reset(),否则结果会累积 -
b.String()是 O(1) 的,它只是把内部[]byte转成字符串视图,不复制数据
子串提取优先用切片语法,少用 strings.Substring(根本不存在这个函数)
Go 没有 strings.Substring —— 这是常见误搜关键词。实际就是用原生切片:s[start:end]。它零分配、O(1),但要注意边界 panic 和底层数据残留。
- 切片不拷贝底层数组,如果只取几个字节却持有 megabytes 级的原始
[]byte,会导致内存无法释放 - 需要长期保存子串时,显式拷贝:
string([]byte(s[start:end]))或strings.Clone(s[start:end])(Go 1.20+) - 用
utf8.RuneCountInString判断中文等多字节字符位置,别直接按字节索引
字符串比较用 ==,别进 strings.EqualFold 或 bytes.Equal 除非真需要
== 对字符串是编译器内建优化操作,直接比头尾指针+长度+字节内容,比 strings.EqualFold 快一个数量级,且无内存分配。
-
strings.EqualFold是为大小写不敏感设计的,走 Unicode 规范映射,慢且重——仅当明确需处理德语 ß、土耳其 i 等场景才用 -
bytes.Equal([]byte(s1), []byte(s2))多余:先转[]byte再比,触发两次堆分配(除非 s 已是常量且编译器能优化掉) - 接口比较(如
interface{})可能隐式装箱,导致指针比较失效,此时应先断言再比
从字节转字符串时,确认是否真需要转换
很多性能问题出在「习惯性转 string」:比如读文件后立刻 string(buf),其实后续只查某个字符或正则匹配,完全可用 bytes 包或 unsafe.String(Go 1.20+)绕过分配。
- 若只做查找、分割、前缀判断,优先用
bytes.Contains、bytes.Split等,参数是[]byte,零分配 - 确定要 string 且 buf 生命周期可控,可用
unsafe.String(&buf[0], len(buf))(无分配,但要求 buf 不被 GC 回收前改写) - HTTP body、JSON 解析等标准库已优化路径,不必手动转;自定义协议解析时才需权衡
最易被忽略的是字符串逃逸到堆上后的生命周期绑定——看着是局部变量,实际因为被闭包捕获或传给 channel,导致整块底层数组卡在内存里很久。这时候光换拼接方式没用,得看数据流怎么穿过的。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











