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

如果我想多次连接字符串而不知道结果字符串的长度,最好的方法是什么?

最天真的做法是:

var s string
for i := 0; i < 1000; i++ {
    s += getShortStringFromSomewhere()
}
return s

但这似乎不是很有效。


当前回答

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

func Append(slice, data[]byte) []byte {
    l := len(slice);
    if l + len(data) > cap(slice) { // reallocate
        // Allocate double what's needed, for future growth.
        newSlice := make([]byte, (l+len(data))*2);
        // Copy data (could use bytes.Copy()).
        for i, c := range slice {
            newSlice[i] = c
        }
        slice = newSlice;
    }
    slice = slice[0:l+len(data)];
    for i, c := range data {
        slice[l+i] = c
    }
    return slice;
}

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

其他回答

在Go 1.10+中有字符串。构建器。

Builder用于使用Write方法有效地构建字符串。它最小化了内存复制。零值就可以使用了。


例子

这与bytes.Buffer几乎相同。

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())
}

点击在操场上看这个。


支持的接口

StringBuilder的方法是在现有接口的基础上实现的。这样您就可以在代码中轻松地切换到新的Builder类型。

Grow(int) ->字节。缓冲#成长 Len() int ->字节。缓冲# Len Reset() ->字节。缓冲#重置 String() String -> fmt. xml斯金格 Write([]byte) (int, error) -> io。作家 WriteByte(byte)错误-> io。ByteWriter WriteRune(rune) (int, error) -> bufio。Writer#WriteRune -字节。缓冲# WriteRune WriteString(string) (int, error) -> io.stringWriter


与字节的区别。缓冲

It can only grow or reset. It has a copyCheck mechanism built-in that prevents accidentially copying it: func (b *Builder) copyCheck() { ... } In bytes.Buffer, one can access the underlying bytes like this: (*Buffer).Bytes(). strings.Builder prevents this problem. Sometimes, this is not a problem though and desired instead. For example: For the peeking behavior when the bytes are passed to an io.Reader etc. bytes.Buffer.Reset() rewinds and reuses the underlying buffer whereas the strings.Builder.Reset() does not, it detaches the buffer.


Note

不要复制StringBuilder值,因为它会缓存底层数据。 如果您想共享一个StringBuilder值,请使用指向它的指针。


更多细节请点击这里查看源代码。

我只是在我自己的代码(递归树遍历)中对上面发布的顶部答案进行了基准测试,简单的concat操作符实际上比BufferString更快。

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()
}

这花了0.81秒,而下面的代码:

func (r *record) String() string {
    s := "(\"" + r.name + "\" ["
    for i := 0; i < len(r.subs); i++ {
        s += r.subs[i].String()
    }
    s += "] " + strconv.FormatInt(r.size,10) + ")\n"
    return s
} 

只花了0.61秒。这可能是由于创建新BufferString的开销。

更新:我还对连接函数进行了基准测试,它在0.54秒内运行。

func (r *record) String() string {
    var parts []string
    parts = append(parts, "(\"", r.name, "\" [" )
    for i := 0; i < len(r.subs); i++ {
        parts = append(parts, r.subs[i].String())
    }
    parts = append(parts, strconv.FormatInt(r.size,10), ")\n")
    return strings.Join(parts,"")
}

这是最快的解决方案,不需要 你首先需要知道或计算总的缓冲区大小:

var data []byte
for i := 0; i < 1000; i++ {
    data = append(data, getShortStringFromSomewhere()...)
}
return string(data)

根据我的基准测试,它比复制解决方案慢了20% (8.1ns / 追加而不是6.72ns),但仍然比使用bytes.Buffer快55%。

我使用以下方法:-

package main

import (
    "fmt"
    "strings"
)

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

strings. join()来自"strings"包

如果你有一个类型不匹配(比如如果你试图连接一个int和一个字符串),你做RANDOMTYPE(你想改变的东西)

EX:

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