Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Golang字符串拼接性能

Golang字符串拼接的性能以及原理详解

作者:Jeson-Sun

最近在做性能优化,有个函数里面的耗时特别长,看里面的操作大多是一些字符串拼接的操作,而字符串拼接在golang里面其实有很多种实现,下面这篇文章主要给大家介绍了关于Golang字符串拼接的性能以及原理的相关资料,需要的朋友可以参考下

1.字符串高效拼接

在go语言中,字符串(string)是不可变的,因此字符串之间的拼接实际上是创建了一个新的字符串。如果频繁的进行字符串拼接,那将会对性能产生严重的影响!

1.1常见的拼接方式

(1)使用 +

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

(2)使用fmt.Sprintf

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

(3) 使用strings.Builder

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

(4) 使用bytes.Buffer

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

(5) 使用[] byte

func byteConcat(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)
}

1.2使用benchmark进行性能对比

测试代码:

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 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) }

测试结果:

$ go test -bench="Concat$" -benchmem .
goos: darwin
goarch: amd64
pkg: example
BenchmarkPlusConcat-8         19      56 ms/op   530 MB/op   10026 allocs/op
BenchmarkSprintfConcat-8      10     112 ms/op   835 MB/op   37435 allocs/op
BenchmarkBuilderConcat-8    8901    0.13 ms/op   0.5 MB/op      23 allocs/op
BenchmarkBufferConcat-8     8130    0.14 ms/op   0.4 MB/op      13 allocs/op
BenchmarkByteConcat-8   17379    0.07 ms/op   0.2 MB/op       2 allocs/op
PASS
ok      example 8.627s

总结: 通过对比,发现fmt.sprintf()+的性能最低,和其他方法相比,性能低了差不多1000倍,且占用内存也比其他方法高了1000倍;而其他三者的性能和占用内存相差不多;性能最高的方法是[]byte,因为提前分配了足够的内存,所以拼接是不会进行字符串的拷贝与内存的重新分配,固效果最佳。

1.3字符串拼接最终建议

综合易用性和性能,一般推荐使用 strings.Builder 来拼接字符串。
官方解释:

A Builder is used to efficiently build a string using Write methods.
It minimizes memory copying.

如果对 strings.Builder 进行内存预分配,性能还可以再次提升。可以使用Grow()来对内存进行预分配。
如:

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

2.性能背后的原理

2.1 比较 strings.Builder和 +

strings.Builder 和 + 性能和内存消耗差距如此巨大,是因为两者的内存分配方式不一样。

10 + 2 * 10 + 3 * 10 + … + 10000 * 10 byte = 500 MB

2.2 比较 strings.Builder 和 bytes.Buffer

strings.Builderbytes.Buffer 底层都是 []byte 数组,但 strings.Builder 性能比 bytes.Buffer 略快约 10% 。一个比较重要的区别在于,bytes.Buffer转化为字符串时重新申请了一块空间,存放生成的字符串变量,而 strings.Builder 直接将底层的[]byte转换成了字符串类型返回了回来。

总结

到此这篇关于Golang字符串拼接的性能以及原理详解的文章就介绍到这了,更多相关Golang字符串拼接性能内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文