String 如何在go中高效地连接字符串

String 如何在go中高效地连接字符串,string,go,string-concatenation,String,Go,String Concatenation,在Go中,string是一种基本类型,这意味着它是只读的,对它的每次操作都会创建一个新字符串 因此,如果我想在不知道结果字符串长度的情况下多次连接字符串,那么最好的方法是什么 天真的做法是: var s string for i := 0; i < 1000; i++ { s += getShortStringFromSomewhere() } return s var的字符串 对于i:=0;i否则使用字节。缓冲区。。。WriteString如果您知道要预分配的字符串的总长度,那

在Go中,
string
是一种基本类型,这意味着它是只读的,对它的每次操作都会创建一个新字符串

因此,如果我想在不知道结果字符串长度的情况下多次连接字符串,那么最好的方法是什么

天真的做法是:

var s string
for i := 0; i < 1000; i++ {
    s += getShortStringFromSomewhere()
}
return s
var的字符串
对于i:=0;i<1000;i++{
s+=getShortStringFrom某处()
}
返回s

但是这似乎不是很有效。

您可以创建一个大的字节片,并使用字符串片将短字符串的字节复制到其中。“有效Go”中给出了一个函数:


然后,当操作完成后,在大字节片上使用
string()
再次将其转换为字符串。

如果您有一个字符串片希望有效地转换为字符串,则可以使用此方法。否则,看看其他答案

strings包中有一个名为
Join
的库函数:

查看
Join
的代码,可以看到一种类似的附加函数的方法,Kinopiko写道:

用法:

import (
    "fmt";
    "strings";
)

func main() {
    s := []string{"this", "is", "a", "joined", "string\n"};
    fmt.Printf(strings.Join(s, " "));
}

$ ./test.bin
this is a joined string
新方式: 从Go 1.10开始,有一个
strings.Builder
类型

老办法: 使用软件包。它有一个实现的类型

主程序包
进口(
“字节”
“fmt”
)
func main(){
变量缓冲区字节。缓冲区
对于i:=0;i<1000;i++{
缓冲区写入限制(“a”)
}
fmt.Println(buffer.String())
}

这在O(n)时间内就完成了。

我刚刚在我自己的代码(递归树行走)中对上面发布的答案进行了基准测试,简单的concat操作符实际上比
缓冲字符串
更快

func (r *record) String() string {
    buffer := bytes.NewBufferString("");
    fmt.Fprint(buffer,"(",r.name,"[")
    for i := 0; i < len(r.subs); i++ {
        fmt.Fprint(buffer,"\t",r.subs[i])
    }
    fmt.Fprint(buffer,"]",r.size,")\n")
    return buffer.String()
}

我最初的建议是

s12 := fmt.Sprint(s1,s2)
但以上答案的使用是最有效的方法

我最初的建议是使用反射和类型开关
正如我天真地认为的那样,基本类型没有通用的Stringer()接口

至少,Sprint()在内部使用bytes.Buffer。因此

`s12 := fmt.Sprint(s1,s2,s3,s4,...,s1000)`
在内存分配方面是可以接受的

=>Sprint()串联可用于快速调试输出。

=>否则使用字节。缓冲区。。。WriteString

如果您知道要预分配的字符串的总长度,那么连接字符串的最有效方法可能是使用内置函数。如果您不知道手边的总长度,请不要使用
复制
,而是阅读其他答案

s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))
在我的测试中,这种方法比使用操作符
+
快约3倍,也快得多(~12000x)。而且,它使用更少的内存

我创建了一个模型来证明这一点,下面是结果:

BenchmarkConcat 1000000 64497 ns/op 502018 B/op 0 allocs/op
基准缓冲区100000000 15.5 ns/op 2 B/op 0 allocs/op
基准副本5000000005.39 ns/op 0 B/op 0 allocs/op

以下是测试代码:

package main

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

func BenchmarkConcat(b *testing.B) {
    var str string
    for n := 0; n < b.N; n++ {
        str += "x"
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); str != s {
        b.Errorf("unexpected result; got=%s, want=%s", str, s)
    }
}

func BenchmarkBuffer(b *testing.B) {
    var buffer bytes.Buffer
    for n := 0; n < b.N; n++ {
        buffer.WriteString("x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); buffer.String() != s {
        b.Errorf("unexpected result; got=%s, want=%s", buffer.String(), s)
    }
}

func BenchmarkCopy(b *testing.B) {
    bs := make([]byte, b.N)
    bl := 0

    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        bl += copy(bs[bl:], "x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); string(bs) != s {
        b.Errorf("unexpected result; got=%s, want=%s", string(bs), s)
    }
}

// Go 1.10
func BenchmarkStringBuilder(b *testing.B) {
    var strBuilder strings.Builder

    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        strBuilder.WriteString("x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); strBuilder.String() != s {
        b.Errorf("unexpected result; got=%s, want=%s", strBuilder.String(), s)
    }
}
主程序包
进口(
“字节”
“字符串”
“测试”
)
func BenchmarkConcat(b*testing.b){
var-str字符串
对于n:=0;n
这是最快的解决方案,不需要 您需要首先知道或计算整个缓冲区大小:

var data []byte
for i := 0; i < 1000; i++ {
    data = append(data, getShortStringFromSomewhere()...)
}
return string(data)
var数据[]字节
对于i:=0;i<1000;i++{
数据=追加(数据,GetShortStringFromwhere()…)
}
返回字符串(数据)
根据我的经验,它比拷贝解决方案慢20%(每分钟8.1ns) 追加而不是6.72ns),但仍比使用bytes.Buffer快55%。

扩展cd1的答案: 您可以使用append()而不是copy()。 append()提供了更大的预先准备,占用了更多的内存,但节省了时间。 我在你的上面加了一句。 本地运行

go test -bench=. -benchtime=100ms
在我的thinkpad T400s上,它可以产生:

BenchmarkAppendEmpty    50000000         5.0 ns/op
BenchmarkAppendPrealloc 50000000         3.5 ns/op
BenchmarkCopy           20000000        10.2 ns/op
strings.Join()
来自“strings”包

如果您的类型不匹配(例如,如果您试图连接一个int和一个string),您可以执行RANDOMTYPE(您想要更改的事情)

例:

输出:

hello all you people in here

这是@cd1(
Go 1.8
linux x86_64
)提供的基准测试的实际版本,并修复了@icza和@PickBoy提到的错误

Bytes.Buffer
仅比通过
+
运算符直接串接快
7倍

package performance_test

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

const (
    concatSteps = 100
)

func BenchmarkConcat(b *testing.B) {
    for n := 0; n < b.N; n++ {
        var str string
        for i := 0; i < concatSteps; i++ {
            str += "x"
        }
    }
}

func BenchmarkBuffer(b *testing.B) {
    for n := 0; n < b.N; n++ {
        var buffer bytes.Buffer
        for i := 0; i < concatSteps; i++ {
            buffer.WriteString("x")
        }
    }
}
2018年增加的注释 从Go 1.10开始,有一个
strings.Builder
类型

201x年前的答案 @cd1的基准代码和其他答案是错误的
b.N
不应在基准功能中设置。它由go测试工具动态设置,以确定测试的执行时间是否稳定

基准函数应该运行相同的测试
b.N
次,循环内的测试对于每个迭代都应该是相同的。所以我通过添加一个内部循环来修复它。我还添加了一些其他so的基准
go test -bench=. -benchtime=100ms
BenchmarkAppendEmpty    50000000         5.0 ns/op
BenchmarkAppendPrealloc 50000000         3.5 ns/op
BenchmarkCopy           20000000        10.2 ns/op
package main

import (
  "fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    out := fmt.Sprintf("%s %s ",str1, str2)
    fmt.Println(out)
}
package main

import (
    "fmt"
    "strings"
)

var intEX = 0
var stringEX = "hello all you "
var stringEX2 = "people in here"


func main() {
    s := []string{stringEX, stringEX2}
    fmt.Println(strings.Join(s, ""))
}
hello all you people in here
package performance_test

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

const (
    concatSteps = 100
)

func BenchmarkConcat(b *testing.B) {
    for n := 0; n < b.N; n++ {
        var str string
        for i := 0; i < concatSteps; i++ {
            str += "x"
        }
    }
}

func BenchmarkBuffer(b *testing.B) {
    for n := 0; n < b.N; n++ {
        var buffer bytes.Buffer
        for i := 0; i < concatSteps; i++ {
            buffer.WriteString("x")
        }
    }
}
BenchmarkConcat-4                             300000          6869 ns/op
BenchmarkBuffer-4                            1000000          1186 ns/op
package main

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

const (
    sss = "xfoasneobfasieongasbg"
    cnt = 10000
)

var (
    bbb      = []byte(sss)
    expected = strings.Repeat(sss, cnt)
)

func BenchmarkCopyPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        bs := make([]byte, cnt*len(sss))
        bl := 0
        for i := 0; i < cnt; i++ {
            bl += copy(bs[bl:], sss)
        }
        result = string(bs)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkAppendPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, cnt*len(sss))
        for i := 0; i < cnt; i++ {
            data = append(data, sss...)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        buf := bytes.NewBuffer(make([]byte, 0, cnt*len(sss)))
        for i := 0; i < cnt; i++ {
            buf.WriteString(sss)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkCopy(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, 64) // same size as bootstrap array of bytes.Buffer
        for i := 0; i < cnt; i++ {
            off := len(data)
            if off+len(sss) > cap(data) {
                temp := make([]byte, 2*cap(data)+len(sss))
                copy(temp, data)
                data = temp
            }
            data = data[0 : off+len(sss)]
            copy(data[off:], sss)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkAppend(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, 64)
        for i := 0; i < cnt; i++ {
            data = append(data, sss...)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferWrite(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var buf bytes.Buffer
        for i := 0; i < cnt; i++ {
            buf.Write(bbb)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferWriteString(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var buf bytes.Buffer
        for i := 0; i < cnt; i++ {
            buf.WriteString(sss)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkConcat(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var str string
        for i := 0; i < cnt; i++ {
            str += sss
        }
        result = str
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}
BenchmarkCopyPreAllocate-8         20000             84208 ns/op          425984 B/op          2 allocs/op
BenchmarkAppendPreAllocate-8       10000            102859 ns/op          425984 B/op          2 allocs/op
BenchmarkBufferPreAllocate-8       10000            166407 ns/op          426096 B/op          3 allocs/op
BenchmarkCopy-8                    10000            160923 ns/op          933152 B/op         13 allocs/op
BenchmarkAppend-8                  10000            175508 ns/op         1332096 B/op         24 allocs/op
BenchmarkBufferWrite-8             10000            239886 ns/op          933266 B/op         14 allocs/op
BenchmarkBufferWriteString-8       10000            236432 ns/op          933266 B/op         14 allocs/op
BenchmarkConcat-8                     10         105603419 ns/op        1086685168 B/op    10000 allocs/op
package main

import (
    "strings"
    "fmt"
)

func main() {
    // ZERO-VALUE:
    //
    // It's ready to use from the get-go.
    // You don't need to initialize it.
    var sb strings.Builder

    for i := 0; i < 1000; i++ {
        sb.WriteString("a")
    }

    fmt.Println(sb.String())
}
package main

import (
    "fmt"
    "strings"
)

func main (){
    concatenation:= strings.Join([]string{"a","b","c"},"") //where second parameter is a separator. 
    fmt.Println(concatenation) //abc
}
package main

import (
"fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    result := make([]byte, 0)
    result = append(result, []byte(str1)...)
    result = append(result, []byte(str2)...)
    result = append(result, []byte(str1)...)
    result = append(result, []byte(str2)...)

    fmt.Println(string(result))
}
go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: github.com/hechen0/goexp/exps
BenchmarkConcat-8                1000000             60213 ns/op          503992 B/op          1 allocs/op
BenchmarkBuffer-8               100000000               11.3 ns/op             2 B/op          0 allocs/op
BenchmarkCopy-8                 300000000                4.76 ns/op            0 B/op          0 allocs/op
BenchmarkStringBuilder-8        1000000000               4.14 ns/op            6 B/op          0 allocs/op
PASS
ok      github.com/hechen0/goexp/exps   70.071s
 func JoinBetween(in []string, separator string, startIndex, endIndex int) string {
    if in == nil {
        return ""
    }

    noOfItems := endIndex - startIndex

    if noOfItems <= 0 {
        return EMPTY
    }

    var builder strings.Builder

    for i := startIndex; i < endIndex; i++ {
        if i > startIndex {
            builder.WriteString(separator)
        }
        builder.WriteString(in[i])
    }
    return builder.String()
}