优化字符串拼接
🟡 中等题目描述
以下三种字符串拼接方式,哪种性能最好?为什么?
// 方式 1:+ 操作符
func concat1(items []string) string {
var result string
for _, item := range items {
result += item
}
return result
}
// 方式 2:fmt.Sprintf
func concat2(items []string) string {
var result string
for _, item := range items {
result = fmt.Sprintf("%s%s", result, item)
}
return result
}
// 方式 3:strings.Builder
func concat3(items []string) string {
var builder strings.Builder
for _, item := range items {
builder.WriteString(item)
}
return builder.String()
}Benchmark 结果
BenchmarkConcat1-8 1000000 1024 ns/op 512 B/op 10 allocs/op
BenchmarkConcat2-8 100000 8192 ns/op 2048 B/op 30 allocs/op
BenchmarkConcat3-8 10000000 128 ns/op 64 B/op 1 allocs/op提示
- Go 字符串是不可变的
- 每次拼接都会创建新字符串
- strings.Builder 使用底层 []byte
解法
参考答案 (3 个标签)
字符串 性能优化 Benchmark
方式 1:+ 操作符
func concat1(items []string) string {
var result string
for _, item := range items {
result += item // 每次创建新字符串
}
return result
}问题:
- 字符串不可变,每次
+都创建新字符串 - 循环 n 次,创建 n 个新字符串
- 时间复杂度:O(n²)
- 空间复杂度:O(n²)
方式 2:fmt.Sprintf
func concat2(items []string) string {
var result string
for _, item := range items {
result = fmt.Sprintf("%s%s", result, item)
}
return result
}问题:
fmt.Sprintf需要解析格式字符串- 反射机制获取参数类型
- 性能比
+更差 - 时间复杂度:O(n²)
方式 3:strings.Builder(推荐)
func concat3(items []string) string {
var builder strings.Builder
builder.Grow(estimateSize(items)) // 预分配
for _, item := range items {
builder.WriteString(item) // 直接写入底层 []byte
}
return builder.String()
}优势:
- 底层使用
[]byte,可变 - 预分配容量,避免扩容
- 只在最后创建一次字符串
- 时间复杂度:O(n)
strings.Builder 底层实现
type Builder struct {
addr *Builder // 检测非法复制
buf []byte // 底层字节切片
}
func (b *Builder) WriteString(s string) (int, error) {
b.buf = append(b.buf, s...) // 直接 append
return len(s), nil
}
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf)) // 零拷贝转换
}关键点:
- 可变底层:使用
[]byte而非string - 零拷贝转换:
String()使用 unsafe 转换 - 预分配:
Grow()避免扩容 - 禁止复制:通过
addr字段检测
性能对比
| 方式 | 时间复杂度 | 分配次数 | 适用场景 |
|---|---|---|---|
+ | O(n²) | O(n) | 简单拼接(2-3 个) |
fmt.Sprintf | O(n²) | O(n) | 需要格式化 |
strings.Builder | O(n) | O(1) | 大量拼接 |
[]byte 转换 | O(n) | O(1) | 底层优化 |
优化建议
// ✅ 最佳实践:预分配容量
func concatOptimized(items []string) string {
// 1. 预估大小
size := 0
for _, item := range items {
size += len(item)
}
// 2. 预分配
var builder strings.Builder
builder.Grow(size)
// 3. 拼接
for _, item := range items {
builder.WriteString(item)
}
return builder.String()
}
// ✅ 替代方案:使用 []byte
func concatWithByte(items []string) string {
size := 0
for _, item := range items {
size += len(item)
}
buf := make([]byte, 0, size)
for _, item := range items {
buf = append(buf, item...)
}
return string(buf)
}扩展:其他字符串操作优化
1. 字符串分割
// ❌ 多次分配
parts := strings.Split(s, ",")
// ✅ 预分配容量
count := strings.Count(s, ",") + 1
parts := make([]string, 0, count)
// 手动分割...2. 子串检查
// ❌ 转换为小写再比较(分配新字符串)
if strings.ToLower(s) == "hello" {
}
// ✅ 使用 EqualFold(不分配)
if strings.EqualFold(s, "hello") {
}3. 字符串拼接(少量)
// ✅ 2-3 个字符串用 +
result := s1 + s2 + s3
// ❌ 不要用 Builder(过度优化)
var builder strings.Builder
builder.WriteString(s1)
builder.WriteString(s2)
builder.WriteString(s3)