1. 程式人生 > 實用技巧 >【原】golang 中字串拼接的方式效能對比

【原】golang 中字串拼接的方式效能對比

背景

開發過程中,常常會用到字串拼接完成某種需求,我們能馬上想到的解決辦法有+,fmt.Sprintf,高階一點可能還會想到strings包的Join 方法,甚至想到bytes.buffer,再用writeString 方法完成,再而想到strings.builder。但究竟哪種效率高呢?我們在使用過程中如何選擇?只有實測才知道

結論

測試結論bytes.Buffer > + > strings.Join > strings.builder > fmt.Sprintf 。

簡單拼接用+,複雜,追求高效用bytes.Buffer

實測

以下測試在個人mac 系統下。go 版本1.15,不同版本可能略有不同,以自己實際輸出為準。

測試1

package strings

import (
"bytes"
"fmt"
"strings"
"testing"
)

func BenchmarkStringBuild(b *testing.B) {
hello := "hello"
world := "world"
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result strings.Builder
result.WriteString(hello)
result.WriteString(world)
//result.WriteString("aaa")
//result.WriteString(strconv.Itoa(10000))
}
}

func BenchmarkStringJia(b *testing.B) {
hello := "hello"
world := "world"
b.ResetTimer()
for i := 0; i < b.N; i++ {
//_ = hello + world + "aaa !" + strconv.Itoa(10000)
_ = hello + world
}
}

func BenchmarkStringSprintf(b *testing.B) {
hello := "hello"
world := "world"
b.ResetTimer()
for i := 0; i < b.N; i++ {
//_ = fmt.Sprintf("%s,%s,%s,%d", hello, world, "aaa !", 1000)
_ = fmt.Sprintf("%s,%s", hello, world)
}
}

func BenchmarkAddStringWithJoin(b *testing.B) {
hello := "hello"
world := "world"
b.ResetTimer()
for i := 0; i < b.N; i++ {
//_ = strings.Join([]string{hello, world, "aaa !", strconv.Itoa(10000)}, ",")
_ = strings.Join([]string{hello, world}, ",")
}
}

func BenchmarkAddStringWithBuffer(b *testing.B) {
hello := "hello"
world := "world"
for i := 0; i < 1000; i++ {
var buffer bytes.Buffer
buffer.WriteString(hello)
buffer.WriteString(",")
buffer.WriteString(world)
//buffer.WriteString("aaa !")
//buffer.WriteString(strconv.Itoa(10000))
_ = buffer.String()
}
}

測試2

package strings

import (
"bytes"
"fmt"
"strconv"
"strings"
"testing"
)

func BenchmarkStringBuild(b *testing.B) {
hello := "hello"
world := "world"
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result strings.Builder
result.WriteString(hello)
result.WriteString(world)
result.WriteString("aaa")
result.WriteString(strconv.Itoa(10000))
}
}

func BenchmarkStringJia(b *testing.B) {
hello := "hello"
world := "world"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = hello + world + "aaa !" + strconv.Itoa(10000)
//_ = hello + world
}
}

func BenchmarkStringSprintf(b *testing.B) {
hello := "hello"
world := "world"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = fmt.Sprintf("%s,%s,%s,%d", hello, world, "aaa !", 1000)
//_ = fmt.Sprintf("%s,%s", hello, world)
}
}

func BenchmarkAddStringWithJoin(b *testing.B) {
hello := "hello"
world := "world"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = strings.Join([]string{hello, world, "aaa !", strconv.Itoa(10000)}, ",")
//_ = strings.Join([]string{hello, world}, ",")
}
}

func BenchmarkAddStringWithBuffer(b *testing.B) {
hello := "hello"
world := "world"
for i := 0; i < 1000; i++ {
var buffer bytes.Buffer
buffer.WriteString(hello)
buffer.WriteString(",")
buffer.WriteString(world)
buffer.WriteString("aaa !")
buffer.WriteString(strconv.Itoa(10000))
_ = buffer.String()
}
}

執行:$ go test -bench=. -benchmem -run=none

結果顯而易見,即便我只拼接hello world,不同方式的對比差異也是顯著的。

可看到

  • string.Builder記憶體分配4次,分配位元組數64位元組
  • + 進行記憶體分配1次,分配位元組數5位元組
  • fmt.Sprintf 進行記憶體分配3次,分配位元組數64位元組
  • strings.Join 進行記憶體分配2次,分配位元組數37位元組
  • bytes.Buffer WriteString進行記憶體分配0次,分配位元組數0

結果一般沒想到的是+ 在1.15版本效能是大於 strings.Join 和fmt.Sprintf的。所以在我們使用中優先選擇bytes.Buffer WriteString、其次簡單的,方便閱讀和編寫可用+。