在平时的开发过程中总会遇到各式各样的字符串拼接场景。本文就从本场景出发,探讨并实验哪一种拼接方法最为高效,以及其背后的底层原理实现。
字符串拼接定义
定义一个方法,实现字符串的拼接,函数签名原型如下:
type concatFn func(...string) string
即传入一系列的字符串,返回其拼接后的结果。
方式1:直接相加
func ConcatWithAdd(strs ...string) string {
var r = ""
for _, v := range strs {
r += v
}
return r
}
StringHeader
type StringHeader struct {
Len int
Data uintptr
}
因此,在golang中,字符串是不可变的,这也导致了一个字符串的相加过程中会创建一个全新的变量来接收拼接后的结果。所以它在内存分配次数上、时间效率上都不是最优的选择。
方式2:使用fmt.Sprintf()
func ConcatWithSprintf(strs ...string) string {
var r = ""
for _, v := range strs {
r = fmt.Sprintf("%s%s", r, v)
}
return r
}
fmt.Sprintf[]bytefmt.Spritf()
方式3:使用strings.Buider
func ConcatWithStringBuilder(strs ...string) string {
var b strings.Builder
for _, v := range strs {
b.WriteString(v)
}
return b.String()
}
strings.Buider[]byte
方式4:使用strings.Join()
func ConcatWithStringsJoin(str ...string) string {
return strings.Join(str, "")
}
strings.Join[]bytestrings.Builder
方式5:使用bytes.Buffer
func ConcatWithByteBuffer(strs ...string) string {
var b bytes.Buffer
for _, v := range strs {
b.WriteString(v)
}
return b.String()
}
bytes.Buffer[]bytestrings.Builderb.String()strings.BuiderString()[]bytebytes.Bufferstrings.Buidler
性能测试
从以上的底层数据类型的分析,我们大致可以知道字符串拼接场景下不同拼接方式的优劣了。
package main
import (
"strconv"
"testing"
)
func prepare() []string {
var r = []string{}
for i := 0; i < 1000; i++ {
r = append(r, strconv.Itoa(i))
}
return r
}
func BenchmarkConcatWithAdd(b *testing.B) {
b.StopTimer()
r := prepare()
b.StartTimer()
for i := 0; i < b.N; i++ {
_ = ConcatWithAdd(r...)
}
}
func BenchmarkConcatWithSprintf(b *testing.B) {
b.StopTimer()
r := prepare()
b.StartTimer()
for i := 0; i < b.N; i++ {
_ = ConcatWithSprintf(r...)
}
}
func BenchmarkConcatWithStringBuilder(b *testing.B) {
b.StopTimer()
r := prepare()
b.StartTimer()
for i := 0; i < b.N; i++ {
_ = ConcatWithStringBuilder(r...)
}
}
func BenchmarkConcatWithByteBuffer(b *testing.B) {
b.StopTimer()
r := prepare()
b.StartTimer()
for i := 0; i < b.N; i++ {
_ = ConcatWithByteBuffer(r...)
}
}
func BenchmarkConcatWithStringsJoin(b *testing.B) {
b.StopTimer()
r := prepare()
b.StartTimer()
for i := 0; i < b.N; i++ {
_ = ConcatWithStringsJoin(r...)
}
}
执行基准测试,可得:
Running tool: /usr/data/go1.17/go/bin/go test -benchmem -run=^$ -coverprofile=/tmp/vscode-goTqWRSX/go-code-cover -bench . demo
goos: linux
goarch: amd64
pkg: demo
cpu: Intel(R) Core(TM) i5-8500 CPU @ 3.00GHz
BenchmarkConcatWithAdd-4 2076 597540 ns/op 1494032 B/op 999 allocs/op
BenchmarkConcatWithSprintf-4 1528 1122768 ns/op 1526913 B/op 2999 allocs/op
BenchmarkConcatWithStringBuilder-4 103244 16315 ns/op 10488 B/op 12 allocs/op
BenchmarkConcatWithByteBuffer-4 77482 27777 ns/op 12464 B/op 8 allocs/op
BenchmarkConcatWithStringsJoin-4 74232 23012 ns/op 3072 B/op 1 allocs/op
PASS
coverage: 100.0% of statements
ok demo 9.102s
结论
fmt.Sprintf()strings.Builderbytes.Buffer[]bytefmt.Sprintfstrings.Joinstrings.Buidlerstrings.Buidlerbyte.Bufferbytes.Bufferstrings.Builder
strings.Builderstrings.Builder
最后,欢迎大家访问本文的网站版本:https://int64.ink/blog/golang字符串拼接的几种方式