在 Go 里:
字符串
string是不可变的(你不能在原来的 string 上直接改)所以 用
+/+=反复拼接,经常会很慢(反复分配 + 复制)为了快,Go 常用两种方式:
- 用
[]byte+append拼- 用
strings.Builder拼(写法更像"拼字符串",性能也好)
cap/Grow都是在做同一件事:提前预留容量,减少扩容拷贝
1. 为什么"拼字符串"会出问题?
你想要构造一个输出,比如:
"blue is sky the"
最直觉的写法是:
go
ans := ""
ans += "blue"
ans += " "
ans += "is"
看起来很合理对吧?
但 Go 的 string 有个关键特性:
✅ Go 的 string 不可变(immutable)
你一旦创建了一个 string,它里面的内容不能"原地修改"。
所以 ans += "blue" 实际发生的是:
- 新开一块内存,长度 = oldLen + newLen
- 把旧的
ans复制进去 - 再把
"blue"复制进去 - 把
ans指向这块新内存
也就是说:每一次 += 都可能要"重新开数组 + 复制一遍旧内容"
2. 这就是为什么 + 会慢:重复拷贝
举个特别直观的例子:
你要拼 3 次:
- 第 1 次:复制 0 个旧字符
- 第 2 次:复制 4 个旧字符("blue")
- 第 3 次:复制 7 个旧字符("blue is")
你会发现:旧内容一直在被反复复制。
拼得越多,复制越多,速度就越慢。
3. 那怎么快?核心思路:用"可变容器"先装起来
既然 string 不可变,那我们先用一个 可变的容器 装字符,最后一次性变成 string。
Go 最常用的可变容器就是:
✅ []byte(字节数组 / 可变)
你可以对它 append,它会自动增长:
go
ans := make([]byte, 0)
ans = append(ans, 'b', 'l', 'u', 'e')
最后:
go
return string(ans)
这样就避免了每次 += 的"重新分配 + 复制旧内容"。
4. 这就引出了:len 和 cap 是啥?
[]byte / []int 这些 slice,在 Go 里有两个重要概念:
len:现在已经用了多少cap:底层总共预留了多少空间(还能装多少)
比如:
go
ans := make([]byte, 0, 10)
意思是:
len = 0(现在里面没东西)cap = 10(底层数组先预留了 10 个位置)
为什么要 cap?
因为如果你不预留,append 可能会这样:
- 空间不够 → 申请更大数组
- 把旧数组内容复制过去
- 才能继续 append
所以:
cap 就是为了减少"扩容 + 拷贝"的次数。
5. 超过 cap 会发生什么?
当你 append 让 len > cap:
- Go 会创建一个更大的新数组
- 把旧内容复制到新数组
- slice 指向新数组
你看:又出现"复制旧内容"了对吧?
所以我们才要 尽量提前预留 cap。
6. 这时候 strings.Builder 登场:更"像拼字符串"的工具
你用 []byte 拼字符串没问题,但写起来像在操作数组。 strings.Builder 是 Go 官方提供的:
专门用来高效拼接字符串的工具
你可以把它理解成:
"官方封装好的 []byte + append"
它的用法是:
go
import "strings"
var b strings.Builder
b.WriteString("blue")
b.WriteByte(' ')
b.WriteString("sky")
result := b.String()
你可以把它理解成:
"内部帮你维护了一个
[]byte的拼接器"
你 WriteString/WriteByte,它内部就在 append 到那个 buffer 里。
最后 String() 一次性输出。
7. 那 Grow 又是什么?它和 cap 的关系是什么?
现在关键来了:
Builder 内部其实也需要容量,否则也会扩容 + 拷贝。
所以它也需要"预留空间"。
这就是:
✅ b.Grow(n)
意思是:
提前保证:接下来还能再写 n 个字节,不用扩容
对应到 slice 的感觉就是:
- slice:
make([]byte, 0, n)预留 cap - builder:
b.Grow(n)预留内部 buffer 的 cap
所以它们关系是:
Grow本质上是在给 Builder 内部的"隐藏 slice"扩cap。
9. 小白怎么选?
你现在阶段记住这个就够了:
✅ 简单场景(拼得不多)
用 + 也行(比如 2~3 次拼接)
✅ 循环里大量拼接(比如这题、构造大字符串)
优先用:
strings.Builder(推荐,易读)
或[]byte+append(你已经在用)
✅ 追求性能时加一条:预留容量
ans := make([]byte, 0, len(s))b.Grow(len(s))