字符串高效拼接
Go語言中为牍,字符串(string)是不可變的辆亏,拼接字符串事實(shí)上是創(chuàng)建了一個新字符串對象蛾狗。
如果代碼大量出現(xiàn)字符串拼接,那么代碼性能將會大大折扣座菠。
拼接字符串方式
-
直接 "+" 拼接
func plusConcat(n int, str string) string { s := "" for i := 0; i < n; i++ { s += str } return s }
-
使用
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 }
-
使用
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() }
-
使用
bytes.Buffer
func bufferConcat(n int, s string) string { buf := new(bytes.Buffer) for i := 0; i < n; i++ { buf.WriteString(s) } return buf.String() }
-
使用
[]byte
func byteConcat(n int, str string) string { buf := make([]byte, 0) for i := 0; i < n; i++ { buf = append(buf, str...) } return string(buf) }
-
使用
strings.Builder
, 且預(yù)分配切片的容量(cap)func builderConcat(n int, str string) string { var builder strings.Builder builder.Grow(n * len(str)) for i := 0; i < n; i++ { builder.WriteString(str) } return builder.String() }
-
使用
[]byte
, 且預(yù)分配切片的容量(cap)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) }
比較效率
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) }
func BenchmarkPreBuilderConcat(b *testing.B) { benchmark(b, preBuilderConcat) }
func BenchmarkPreByteConcat(b *testing.B) { benchmark(b, preByteConcat) }
##### 執(zhí)行 `go test -bench="Concat$" -benchmem .`
goos: windows
goarch: amd64
pkg: hello
cpu: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
BenchmarkPlusConcat-12 13 88328838 ns/op 530999171 B/op 10038 allocs/op
BenchmarkSprintfConcat-12 7 154641671 ns/op 832944258 B/op 34091 allocs/op
BenchmarkBuilderConcat-12 9126 132454 ns/op 514803 B/op 23 allocs/op
BenchmarkBufferConcat-12 9463 115917 ns/op 368577 B/op 13 allocs/op
BenchmarkByteConcat-12 8782 144326 ns/op 621299 B/op 24 allocs/op
BenchmarkPreBuilderConcat-12 24392 50559 ns/op 106496 B/op 1 allocs/op
BenchmarkPreByteConcat-12 20532 60857 ns/op 212993 B/op 2 allocs/op
PASS
直接使用
'+'
拼接效率很低狸眼,且分配內(nèi)存次數(shù)高達(dá)10038次,占用高達(dá)530999171 B浴滴。
執(zhí)行'+'
拼接10000次拓萌,且每次都需要給新字符串申請內(nèi)存。使用
fmt.Sprintf()
效率比 直接使用'+'
拼接更低升略,因?yàn)橥ǔ?fmt.Sprintf()
是用來格式化字符串的-
使用
strings.Builder
微王、bytes.Buffer
、[]byte
效率相似品嚣,且效率遠(yuǎn)大于直接使用 '+' 拼接字符串炕倘。
使用strings.Builder
與 使用 '+' 的主要區(qū)別在于內(nèi)存分配次數(shù)。- 使用 '+' 拼接字符串翰撑,拼接n次就要申請n次內(nèi)存罩旋,申請內(nèi)存大小(B)=10 + 210 + 310 + ... + 10000*10
- 使用
strings.Builder
拼接字符串,拼接n次需要內(nèi)存>10*10000眶诈,申請內(nèi)存大小(B)=16 + 32 + 64 + ... + 122880
使用
strings.Builder
且預(yù)分配內(nèi)存大小涨醋,效率則會比不預(yù)分配內(nèi)存翻倍, 例如preBuilderConcat
preBuilderConcat
比preByteConcat
少一次內(nèi)存分配是因?yàn)?bytes.Buffer
轉(zhuǎn)化為字符串時重新申請了一塊空間册养,存放生成的字符串變量东帅,而strings.Builder
直接將底層的[]byte
轉(zhuǎn)換成了字符串類型返回了回來压固。