郑文峰的博客 郑文峰的博客
首页
  • Go语言高性能编程
分类
标签
归档
关于
  • 导航 (opens new window)
  • 代码片段 (opens new window)
  • 收藏
  • 友链
  • 外部页面

    • 开往 (opens new window)
GitHub (opens new window)

zhengwenfeng

穷则变,变则通,通则久
首页
  • Go语言高性能编程
分类
标签
归档
关于
  • 导航 (opens new window)
  • 代码片段 (opens new window)
  • 收藏
  • 友链
  • 外部页面

    • 开往 (opens new window)
GitHub (opens new window)
  • python

  • go语言

    • go简单使用grpc
    • gin中validator模块的源码分析
    • 优化gin表单的错误提示信息
    • go中如何处理error
    • tcp缓存引起的日志丢失
    • 使用etcd分布式锁导致的协程泄露与死锁问题
    • go语言高性能编程

      • Go协程池深度解析:原理、实现与最佳实践
      • Go语言Interface Boxing原理与性能优化指南
      • Go语言遍历性能深度解析:从原理到优化实践
      • Go语言零拷贝技术完全指南
      • Go语言不可变数据共享:无锁并发编程实践
      • Go语言内存预分配完全指南
      • Go语言原子操作完全指南
      • Go语言堆栈分配与逃逸分析深度解析
      • Go语言空结构体:零内存消耗的高效编程
      • Go语言结构体内存对齐完全指南
      • Go语言字符串拼接性能对比与优化指南
        • 简介
        • 基准测试大比拼
        • 性能分析
        • 总结
      • Go语言延迟初始化(Lazy Initialization)最佳实践
      • Go语言高效IO缓冲技术详解
  • linux

  • 其他

  • 编程
  • go语言
  • go语言高性能编程
zhengwenfeng
2025-06-14
目录

Go语言字符串拼接性能对比与优化指南

# 简介

在Go语言中,常见的字符串拼接方式有以下6种:

  1. +号拼接:最简单的拼接方式
  2. fmt.Sprintf:格式化拼接
  3. strings.Builder:专门优化的字符串构建器
  4. bytes.Buffer:字节缓冲区
  5. []byte转换:字节切片转换
  6. 预分配[]byte:预先分配足够空间的字节切片

本文主要是针对上面各种方式进行基准测试,帮助我们在以后的编码过程中选择最优的方式。

# 基准测试大比拼

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func randomString(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}

func plusConcat(n int, str string) string {
	s := ""
	for i := 0; i < n; i++ {
		s += str
	}
	return s
}

func sprintfConcat(n int, str string) string {
	s := ""
	for i := 0; i < n; i++ {
		s = fmt.Sprintf("%s%s", s, str)
	}
	return s
}

func builderConcat(n int, str string) string {
	var builder strings.Builder
	for i := 0; i < n; i++ {
		builder.WriteString(str)
	}
	return builder.String()
}

func bufferConcat(n int, s string) string {
	buf := new(bytes.Buffer)
	for i := 0; i < n; i++ {
		buf.WriteString(s)
	}
	return buf.String()
}

func byteConcat(n int, str string) string {
	buf := make([]byte, 0)
	for i := 0; i < n; i++ {
		buf = append(buf, str...)
	}
	return string(buf)
}

func preByteConcat(n int, str string) string {
	buf := make([]byte, 0, n*len(str))
	for i := 0; i < n; i++ {
		buf = append(buf, str...)
	}
	return string(buf)
}

func benchmark(b *testing.B, f func(int, string) string) {
	var str = randomString(10)
	for i := 0; i < b.N; i++ {
		f(10000, str)
	}
}

func BenchmarkPlusConcat(b *testing.B)    { benchmark(b, plusConcat) }
func BenchmarkSprintfConcat(b *testing.B) { benchmark(b, sprintfConcat) }
func BenchmarkBuilderConcat(b *testing.B) { benchmark(b, builderConcat) }
func BenchmarkBufferConcat(b *testing.B)  { benchmark(b, bufferConcat) }
func BenchmarkByteConcat(b *testing.B)    { benchmark(b, byteConcat) }
func BenchmarkPreByteConcat(b *testing.B) { benchmark(b, preByteConcat) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

结果如下:

go test  -bench=. -benchmem .
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkPlusConcat-12                36          32238928 ns/op        530999857 B/op     10045 allocs/op
BenchmarkSprintfConcat-12             20          56848854 ns/op        833393446 B/op     34184 allocs/op
BenchmarkBuilderConcat-12          23143             51218 ns/op          514804 B/op         23 allocs/op
BenchmarkBufferConcat-12           29152             40997 ns/op          368578 B/op         13 allocs/op
BenchmarkByteConcat-12             19549             60984 ns/op          621301 B/op         24 allocs/op
BenchmarkPreByteConcat-12          50913             24879 ns/op          212994 B/op          2 allocs/op
PASS
ok      main/demo       10.263s
1
2
3
4
5
6
7
8
9
10
11
12
13

# 性能分析

+号和fmt.Sprintf性能差的原因:

  1. 每次操作都会创建新的字符串
  2. 产生大量内存分配和垃圾回收压力

strings.Builder/bytes.Buffer表现好的原因

  1. 内部使用[]byte缓冲区
  2. 按需扩容,减少内存分配次数

预分配[]byte最佳的原因

  1. 一次性分配足够内存
  2. 完全避免了扩容带来的性能损耗

# 总结

通过测试我们发现:

  • 预分配[]byte性能最佳,适合高性能场景
  • strings.Builder是通用场景的最佳选择
  • +号和fmt.Sprintf在循环拼接中性能极差

在实际开发中,应根据场景选择合适的方法,在代码可读性和性能之间取得平衡。

上次更新: 2025/06/14, 16:16:07
Go语言结构体内存对齐完全指南
Go语言延迟初始化(Lazy Initialization)最佳实践

← Go语言结构体内存对齐完全指南 Go语言延迟初始化(Lazy Initialization)最佳实践→

最近更新
01
Go语言高效IO缓冲技术详解
06-14
02
Go语言延迟初始化(Lazy Initialization)最佳实践
06-14
03
Go语言结构体内存对齐完全指南
06-14
更多文章>
Theme by Vdoing | Copyright © 2022-2025 zhengwenfeng | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式