字符串连接哪一种方式最高效

Go里面string是最基础的类型,是一个只读类型,针对他的每一个操作都会创建一个新的string


所以,如果我在不知道结果是多少长字符串的情况下不断的连接字符串,怎么样的方式是最好的呢?


最常用的可能是如下这样:


s := ""
for i := 0; i < 1000; i++ {
s += otherString()
}
return s

但是这样好像非常的慢

已邀请:

sheepbao - 流媒体,分布式,即时通信

赞同来自: astaxie lwhile davidcai1993 wida haohongfan 小蚂蚁 bvaccc raindylong更多 »

package main

import (
"bytes"
"fmt"
"strings"
"time"
)

var way map[int]string

func benchmarkStringFunction(n int, index int) (d time.Duration) {
v := "ni shuo wo shi bu shi tai wu liao le a?"
var s string
var buf bytes.Buffer

t0 := time.Now()
for i := 0; i < n; i++ {
switch index {
case 0: // fmt.Sprintf
s = fmt.Sprintf("%s[%s]", s, v)
case 1: // string +
s = s + "[" + v + "]"
case 2: // strings.Join
s = strings.Join([]string{s, "[", v, "]"}, "")
case 3: // stable bytes.Buffer
buf.WriteString("[")
buf.WriteString(v)
buf.WriteString("]")
}

}
d = time.Since(t0)
if index == 3 {
s = buf.String()
}
fmt.Printf("string len: %d\t", len(s))
fmt.Printf("time of [%s]=\t %v\n", way[index], d)
return d
}

func main() {
way = make(map[int]string, 5)
way[0] = "fmt.Sprintf"
way[1] = "+"
way[2] = "strings.Join"
way[3] = "bytes.Buffer"

k := 4
d := [5]time.Duration{}
for i := 0; i < k; i++ {
d[i] = benchmarkStringFunction(10000, i)
}
}

结果:


string len: 410000      time of [fmt.Sprintf]=   426.001476ms                                                      
string len: 410000 time of [+]= 307.044147ms
string len: 410000 time of [strings.Join]= 738.44362ms
string len: 410000 time of [bytes.Buffer]= 742.248µs


  • strings.Join 最慢

  • fmt.Sprintf 和 string + 差不多

  • bytes.Buffer又比上者快约500倍

傅小黑

赞同来自: qi19901212 xieyanke tupunco 小蚂蚁 皇虫

如果是少量小文本拼接,用 “+” 就好


如果是大量小文本拼接,用 strings.Join


如果是大量大文本拼接,用 bytes.Buffer

lwhile

赞同来自: sheepbao cz000 sundyli

分享StackOverflow上的一个类似问题下的答案.在作者的测试中,bytes.Buffer的性能并不是最好的.
How to efficiently concatenate strings in Go?

stevewang

赞同来自: judas sundyli

应该用strings.Join,而且和+bytes.Buffer相比,strings.Join是最快的,bytes.Buffer的性能略差但是接近,+最慢是strings.Join执行时间的两倍还多。


为什么strings.Join最快?看看它的源码就知道了,很难用go语言实现一个更快的函数了。它的实现是先计算出要分配的内存空间,然后依次复制字符串。几乎完全没有多余的内存分配也没有多余的字符串拷贝,还能再怎么快呢?
另外,测试还是应该用testing.B而不是自己去写测试框架;而且,测试性能要避免I/O的影响。


我的测试结果是:


strings.Join:
10000000 275 ns/op
bytes.Buffer:
5000000 388 ns/op
+:
2000000 985 ns/op

测试代码如下:


package main

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

var (
strs = []string{
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"ten",
}
)

func TestStringsJoin(b *testing.B) {
for i := 0; i < b.N; i++ {
strings.Join(strs, "")
}
}

func TestStringsPlus(b *testing.B) {
for i := 0; i < b.N; i++ {
var s string
for j := 0; j < len(strs); j++ {
s += strs[j]
}
}
}

func TestBytesBuffer(b *testing.B) {
for i := 0; i < b.N; i++ {
var b bytes.Buffer
for j := 0; j < len(strs); j++ {
b.WriteString(strs[j])
}
}
}

func main() {
fmt.Println("strings.Join:")
fmt.Println(testing.Benchmark(TestStringsJoin))
fmt.Println("bytes.Buffer:")
fmt.Println(testing.Benchmark(TestBytesBuffer))
fmt.Println("+:")
fmt.Println(testing.Benchmark(TestStringsPlus))
}

tupunco

赞同来自: kylefeng

学习过 JAVA 或者 C# 就知道, 拼长字符串用 StringBuilder/StringBuffer.
gobytes.Buffer 性能最好.

mintzhao - 区块链开发者

赞同来自: tupunco

fmt.Sprintf("%s%s", "abc", "def")

在99%的业务场景中,考虑这些完全是吹毛求疵吧

willee

赞同来自: CodyGuo

利用bytes.buffer,消耗时间最短

lwhile

赞同来自:

strings.Join()并不是用来做字符串拼接的.


如果对字符串的拼接有效率要求,那么最好转换成字节来操作.


s1 := ""
for i := 0; i < 100000; i++ {
s1 += "test"
}
//6.824335291s

s2 := ""
var b2 []byte
b2 = append(b2,[]byte(s2)...)
for i:=0; i < 100000; i++ {
b2 = append(b2, []byte("test")...)
}
s2 = string(b2)
//5.885215ms

2gua - 码农2gua

赞同来自:

推荐strings.Join。

philc - https://github.com/philchia

赞同来自:


func JoinStrings(strs ...string) string {
ln := 0
for i := 0; i < len(strs); i++ {
ln += len(strs[i])
}
bts := make([]byte, ln)
ln = 0
for _, str := range strs {
ln += copy(bts[ln:], str)
}

return string(bts)
}

qi19901212 - 一个喜欢讲产品代码敲的不怎么样的.......

赞同来自:


strings.Join(a []string, sep string)


sheepbao - 流媒体,分布式,即时通信

赞同来自:

我记得有人测试过“+”,应该也不慢,可以试试fmt.Sprintf()

要回复问题请先登录注册