背景
开发过程中,常常会用到字符串拼接完成某种需求,我们能马上想到的解决办法有+,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、其次简单的,方便阅读和编写可用+。