string 如何在go中有效地连接字符串

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1760757/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 00:34:39  来源:igfitidea点击:

How to efficiently concatenate strings in go

stringgostring-concatenation

提问by Randy Sugianto 'Yuku'

In Go, a stringis a primitive type, which means it is read-only, and every manipulation of it will create a new string.

在 Go 中, astring是一个原始类型,这意味着它是只读的,每次对其进行操作都会创建一个新字符串。

So if I want to concatenate strings many times without knowing the length of the resulting string, what's the best way to do it?

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

The naive way would be:

天真的方法是:

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

but that does not seem very efficient.

但这似乎不是很有效。

回答by marketer

New Way:

新方法:

From Go 1.10 there is a strings.Buildertype, please take a look at this answer for more detail.

从 Go 1.10 开始,有一种strings.Builder类型,请查看此答案以获取更多详细信息

Old Way:

旧方式:

Use the bytespackage. It has a Buffertype which implements io.Writer.

使用bytes包。它有一个Buffer实现io.Writer.

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var buffer bytes.Buffer

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

    fmt.Println(buffer.String())
}

This does it in O(n) time.

这是在 O(n) 时间内完成的。

回答by cd1

The most efficient way to concatenate strings is using the builtin function copy. In my tests, that approach is ~3x faster than using bytes.Bufferand much much faster (~12,000x) than using the operator +. Also, it uses less memory.

连接字符串的最有效方法是使用内置函数copy。在我的测试中,这种方法比 using 快 3 倍,比使用bytes.Bufferoperator 快得多(~12,000 倍)+。此外,它使用更少的内存。

I've created a test caseto prove this and here are the results:

我创建了一个测试用例来证明这一点,结果如下:

BenchmarkConcat  1000000    64497 ns/op   502018 B/op   0 allocs/op
BenchmarkBuffer  100000000  15.5  ns/op   2 B/op        0 allocs/op
BenchmarkCopy    500000000  5.39  ns/op   0 B/op        0 allocs/op


Below is code for testing:

下面是测试代码:

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

回答by Inanc Gumus

Beginning with Go 1.10 there is a strings.Builder, here.

从 Go 1.10 开始strings.Builder这里有一个, 。

A Builder is used to efficiently build a string using Write methods. It minimizes memory copying. The zero value is ready to use.

Builder 用于使用 Write 方法有效地构建字符串。它最大限度地减少了内存复制。零值即可使用。



Usage:

用法:

It's almost the same with bytes.Buffer.

bytes.Buffer.

package main

import (
    "strings"
    "fmt"
)

func main() {
    var str strings.Builder

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

    fmt.Println(str.String())
}

Note:Do not copy a StringBuilder value as it caches the underlying data. If you want to share a StringBuilder value, use pointers.

注意:不要复制 StringBuilder 值,因为它会缓存底层数据。如果要共享 StringBuilder 值,请使用指针。



StringBuilder methods and interfaces it supports:

它支持的 StringBuilder 方法和接口:

Its methods are being implemented with the existing interfaces in mind so that you can switch to the new Builder easily in your code.

它的方法是在考虑现有接口的情况下实现的,因此您可以在代码中轻松切换到新的 Builder。



Zero value usage:

零值使用:

var buf strings.Builder


Differences from bytes.Buffer:

与 bytes.Buffer 的区别:

  • It can only grow or reset.

  • In bytes.Buffer, one can access the underlying bytes like this: (*Buffer).Bytes(); strings.Builderprevents this problem. Sometimes, this is not a problem though and desired instead (for example, for peeking behavior when the bytes are passed to an io.Readeretc).

  • It also has a copyCheck mechanism built-in that prevents accidentially copying it (func (b *Builder) copyCheck() { ... }).

  • 它只能增长或重置。

  • bytes.Buffer,一个可以访问这样的底层字节:(*Buffer).Bytes(); strings.Builder防止这个问题。有时,这不是问题,而是需要的(例如,当字节传递给io.Reader等时的窥视行为)。

  • 它还具有内置的 copyCheck 机制,可防止意外复制它 ( func (b *Builder) copyCheck() { ... })。



Check out its source code here.

在此处查看其源代码

回答by mbarkhau

There is a library function in the strings package called Join: http://golang.org/pkg/strings/#Join

字符串包中有一个库函数叫做Joinhttp: //golang.org/pkg/strings/#Join

A look at the code of Joinshows a similar approach to Append function Kinopiko wrote: https://golang.org/src/strings/strings.go#L420

看一下代码Join显示了类似于 Kinopiko 所写的 Append 函数的方法:https://golang.org/src/strings/strings.go#L420

Usage:

用法:

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

回答by JasonMc

I just benchmarked the top answer posted above in my own code (a recursive tree walk) and the simple concat operator is actually faster than the BufferString.

我只是在我自己的代码(递归树遍历)中对上面发布的最佳答案进行了基准测试,并且简单的 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()
}

This took 0.81 seconds, whereas the following code:

这花了 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
} 

only took 0.61 seconds. This is probably due to the overhead of creating the new BufferString.

只用了 0.61 秒。这可能是由于创建新的BufferString.

Update:I also benchmarked the joinfunction and it ran in 0.54 seconds.

更新:我还对该join函数进行了基准测试,它在 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,"")
}

回答by rog

This is the fastest solution that does not require you to know or calculate the overall buffer size first:

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

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

By my benchmark, it's 20% slower than the copy solution (8.1ns per append rather than 6.72ns) but still 55% faster than using bytes.Buffer.

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

回答by rog

You could create a big slice of bytes and copy the bytes of the short strings into it using string slices. There is a function given in "Effective Go":

您可以创建一大块字节并使用字符串切片将短字符串的字节复制到其中。“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;
}

Then when the operations are finished, use string ( )on the big slice of bytes to convert it into a string again.

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

回答by harold ramos

package main

import (
  "fmt"
)

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

回答by PickBoy

Note added in 2018

2018 年添加的注释

From Go 1.10 there is a strings.Buildertype, please take a look at this answer for more detail.

从 Go 1.10 开始,有一种strings.Builder类型,请查看此答案以获取更多详细信息

Pre-201x answer

201x 之前的答案

The benchmark code of @cd1 and other answers are wrong. b.Nis not supposed to be set in benchmark function. It's set by the go test tool dynamically to determine if the execution time of the test is stable.

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

A benchmark function should run the same test b.Ntimes and the test inside the loop should be the same for each iteration. So I fix it by adding an inner loop. I also add benchmarks for some other solutions:

基准函数应该运行相同的测试b.N时间,并且循环内的测试对于每次迭代应该是相同的。所以我通过添加一个内循环来修复它。我还为其他一些解决方案添加了基准:

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

Environment is OS X 10.11.6, 2.2 GHz Intel Core i7

环境为 OS X 10.11.6, 2.2 GHz Intel Core i7

Test results:

检测结果:

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

Conclusion:

结论:

  1. CopyPreAllocateis the fastest way; AppendPreAllocateis pretty close to No.1, but it's easier to write the code.
  2. Concathas really bad performance both for speed and memory usage. Don't use it.
  3. Buffer#Writeand Buffer#WriteStringare basically the same in speed, contrary to what @Dani-Br said in the comment. Considering stringis indeed []bytein Go, it makes sense.
  4. bytes.Buffer basically use the same solution as Copywith extra book keeping and other stuff.
  5. Copyand Appenduse a bootstrap size of 64, the same as bytes.Buffer
  6. Appenduse more memory and allocs, I think it's related to the grow algorithm it use. It's not growing memory as fast as bytes.Buffer
  1. CopyPreAllocate是最快的方式;AppendPreAllocate非常接近No.1,但更容易编写代码。
  2. Concat在速度和内存使用方面都有非常糟糕的表现。不要使用它。
  3. Buffer#Write并且Buffer#WriteString速度基本相同,与@Dani-Br 在评论中所说的相反。考虑到string确实[]byte在 Go 中,这是有道理的。
  4. bytes.Buffer 基本上使用Copy与额外簿记和其他东西相同的解决方案。
  5. CopyAppend使用 64 的引导程序大小,与 bytes.Buffer 相同
  6. Append使用更多的内存和分配,我认为这与它使用的增长算法有关。它的内存增长速度不如 bytes.Buffer

Suggestion:

建议:

  1. For simple task such as what OP wants, I would use Appendor AppendPreAllocate. It's fast enough and easy to use.
  2. If need to read and write the buffer at the same time, use bytes.Bufferof course. That's what it's designed for.
  1. 对于简单的任务,例如 OP 想要什么,我会使用AppendAppendPreAllocate。它足够快且易于使用。
  2. 如果需要同时读写缓冲区bytes.Buffer,当然使用。这就是它的设计目的。

回答by Peter Buchmann

My original suggestion was

我最初的建议是

s12 := fmt.Sprint(s1,s2)

But above answer using bytes.Buffer - WriteString()is the most efficient way.

但是上面使用bytes.Buffer - WriteString() 的答案是最有效的方法。

My initial suggestion uses reflection and a type switch. See (p *pp) doPrintand (p *pp) printArg
There is no universal Stringer() interface for basic types, as I had naively thought.

我最初的建议使用反射和类型开关。请参阅(p *pp) doPrint(p *pp) printArg
我天真地认为,基本类型没有通用的 Stringer() 接口。

At least though, Sprint() internallyuses a bytes.Buffer. Thus

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

`s12 := fmt.Sprint(s1,s2,s3,s4,...,s1000)`

is acceptable in terms of memory allocations.

在内存分配方面是可以接受的。

=> Sprint() concatenation can be used for quick debug output.
=> Otherwise use bytes.Buffer ... WriteString

=> Sprint() 串联可用于快速调试输出。
=> 否则使用 bytes.Buffer ... WriteString