【问题标题】:How to efficiently concatenate strings in go如何在go中有效地连接字符串
【发布时间】:2010-12-18 03:51:09
【问题描述】:

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

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

天真的方法是:

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

但这似乎效率不高。

【问题讨论】:

  • 再来一个bench
  • 注意:这个问题和大多数答案似乎是在append() 进入语言之前写的,这是一个很好的解决方案。它将像copy() 一样快速执行,但会首先增长切片,即使这意味着如果容量不足则分配一个新的支持数组。 bytes.Buffer 如果你想要它的额外便利方法或者你使用的包需要它,它仍然是有意义的。
  • 不仅仅是“看起来效率很低”;它有一个特定的问题,即我们在工作的最初几周内遇到的每一个新的非 CS 员工。它是二次的 - O(n*n)。想想数字序列:1 + 2 + 3 + 4 + ...。它是n*(n+1)/2,底边三角形的面积n。当您在循环中附加不可变字符串时,您分配大小 1,然后是大小 2,然后是大小 3,等等。这种二次方的资源消耗表现在更多方面。

标签: string go string-concatenation


【解决方案1】:

简单易消化的解决方案。 cmets中的详细信息。 复制覆盖切片的元素。我们正在对单个元素进行切片并覆盖它。

package main

import (
    "fmt"
)

var N int = 100000

func main() {
    slice1 := make([]rune, N, N)
    //Efficient with fast performance, Need pre-allocated memory
    //We can add a check if we reached the limit then increase capacity
    //using append, but would be fined for data copying to new array. Also append happens after the length of current slice.
    for i := 0; i < N; i++ {
        copy(slice1[i:i+1], []rune{'N'})
    }
    fmt.Println(slice1)

    //Simple but fast solution, Every time the slice capacity is reached we get a fine of effort that goes
    //in copying data to new array
    slice2 := []rune{}
    for i := 0; i <= N; i++ {
        slice2 = append(slice2, 'N')
    }
    fmt.Println(slice2)

}

【讨论】:

    【解决方案2】:
    推荐的答案 Go Language

    在 Go 1.10+ 中有 strings.Builderhere

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

    Click to see this on the playground.


    支持的接口

    StringBuilder 的方法是在考虑现有接口的情况下实现的。这样您就可以在代码中轻松切换到新的 Builder 类型。


    与 bytes.Buffer 的区别

    • 它只能增长或重置。

    • 它有一个内置的 copyCheck 机制,可以防止意外复制它:

      func (b *Builder) copyCheck() { ... }

    • bytes.Buffer 中,可以像这样访问底层字节:(*Buffer).Bytes()

      • strings.Builder 可以防止这个问题。
      • 有时,这不是问题,而是需要。
      • 例如:当字节被传递给io.Reader等时的偷看行为。
    • bytes.Buffer.Reset() rewinds and reuses 是底层缓冲区,而 strings.Builder.Reset() does not 会分离缓冲区。


    注意

    • 不要复制 StringBuilder 值,因为它会缓存基础数据。
    • 如果要共享 StringBuilder 值,请使用指向它的指针。

    查看其源代码了解更多详情,here

    【讨论】:

    • “逃跑”是什么意思?你的意思是字符串中的转义,还是只是可以暴露底层字节?
    • @makhdumi 是的,第二次,暴露底层字节。
    • 值得注意的是strings.Builder 使用指针接收器来实现它的方法,这让我愣了一下。因此,我可能会使用new 创建一个。
    • @DuncanJones 我已经添加了一个注释,但是,由于它主要用于缓存数据,因此在跨 func 等共享它时使用指向它的指针是正常的。在同一个 func 中,您可以使用它也可以作为非指针。
    • 另一个可能很重要的区别:strings.Builder.Reset() 将底层切片设置为nil(没有内存重用)。其中bytes.Buffer.Reset()[]bytes 设置为零长度,保持底层数组的分配。在sync.Pool 中重用strings.Builder 时,这让我有点吃惊,这似乎完全没用。
    【解决方案3】:

    如果您想要将字符串切片高效地转换为字符串,那么您可以使用这种方法。否则,请查看其他答案。

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

    查看Join 的代码显示了与Append 函数相似的方法 Kinopiko 写道:https://golang.org/src/strings/strings.go#L420

    用法:

    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
    

    【讨论】:

    • 当您必须遍历不是 [] 字符串的内容时不起作用。
    【解决方案4】:

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

    在我的测试中,这种方法比使用 bytes.Buffer 快约 3 倍,比使用运算符 + 快得多(约 12,000 倍)。此外,它使用的内存更少。

    我创建了a test case 来证明这一点,结果如下:

    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
    

    下面是测试代码:

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

    【讨论】:

    • bytes.Buffer 应该与副本基本相同(我猜还有一些额外的簿记),并且速度没有那么不同。所以我会用那个:)。不同之处在于缓冲区以 0 字节开始,因此它必须重新分配(我猜这让它看起来有点慢)。不过更容易使用。
    • buffer.Write(字节)比 buffer.WriteString 快 30%。 [如果您能以[]byte 获取数据,则很有用]
    • 请注意,基准测试结果失真且不真实。不同的基准函数将使用不同的b.N 值调用,因此您不会比较要执行的同一任务的执行时间(例如,一个函数可能附加1,000 字符串,另一个可能附加10,000这会对 1 次追加的平均时间产生很大影响,例如 BenchmarkConcat())。您应该在每种情况下使用相同的附加计数(当然不是b.N),并在for 的主体内进行所有连接,范围为b.N(即嵌入2 个for 循环)。跨度>
    • 此外,复制基准通过显式忽略分配所花费的时间而产生偏差,该时间包含在其他基准中。
    • 此外,复制基准依赖于知道结果字符串的长度。
    【解决方案5】:

    新方式:

    从 Go 1.10 开始,有一个 strings.Builder 类型,please take a look at this answer for more detail

    旧方式:

    使用bytes 包。它有一个实现io.WriterBuffer 类型。

    package main
    
    import (
        "bytes"
        "fmt"
    )
    
    func main() {
        var buffer bytes.Buffer
    
        for i := 0; i < 1000; i++ {
            buffer.WriteString("a")
        }
    
        fmt.Println(buffer.String())
    }
    

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

    【讨论】:

    • 而不是 println(string(buffer.Bytes()));使用可以只做 println(buffer.String())
    • 你可以用var buffer bytes.Buffer代替buffer := bytes.NewBufferString("")。您也不需要任何这些分号:)。
    • 难以置信的快。在我的程序中将一些天真的“+”字符串连接从 3 分钟缩短到 1.3
    • +1 表示“O(n) 时间”;我认为多发表这样的言论很重要。
    • Go 1.10 添加了strings.Builder,类似于 bytes.Buffer 但当您的最终目标是字符串时速度更快。
    【解决方案6】:

    strings.Join() 来自“字符串”包

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

    前:

    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
    

    【讨论】:

    • 这段代码甚至不能编译:strings.Join() 只接受两个参数:一个切片和一个分隔符string
    • 这无济于事
    • 在此处添加一些更改。
    【解决方案7】:

    2018 年添加的注释

    从 Go 1.10 开始,有一个 strings.Builder 类型,please take a look at this answer for more detail

    201x 之前的答案

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

    基准函数应该运行相同的测试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)
        }
    }
    

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

    测试结果:

    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
    

    结论:

    1. CopyPreAllocate 是最快的方式; AppendPreAllocate 与 No.1 很接近,但写代码更容易。
    2. Concat 在速度和内存使用方面的表现都非常差。不要使用它。
    3. Buffer#WriteBuffer#WriteString 在速度上基本相同,与@Dani-Br 在评论中所说的相反。考虑到 string 在 Go 中确实是 []byte,这是有道理的。
    4. bytes.Buffer 基本上使用与Copy 相同的解决方案,但有额外的簿记和其他内容。
    5. CopyAppend 使用 64 的引导大小,与 bytes.Buffer 相同
    6. Append 使用更多内存和分配,我认为这与它使用的增长算法有关。它的内存增长速度不如 bytes.Buffer

    建议:

    1. 对于诸如 OP 想要的简单任务,我会使用 AppendAppendPreAllocate。它足够快且易于使用。
    2. 如果需要同时读写缓冲区,当然使用bytes.Buffer。这就是它的设计目的。

    【讨论】:

      【解决方案8】:

      我刚刚在我自己的代码(递归树遍历)中对上面发布的最佳答案进行了基准测试,简单的 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 的开销所致。

      更新:我还对 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,"")
      }
      

      【讨论】:

      • 我相信 OP 更关心内存复杂性而不是运行时复杂性,因为天真的字符串连接每次都会导致新的内存分配。
      • 这个速度慢可能与使用 fmt.Fprint 而不是buffer.WriteString("\t");buffer.WriteString(subs[i]);有关
      • 我很高兴知道我首选的方法(strings.Join) 运行速度最快,而this(bytes.Buffer) 是赢家!
      【解决方案9】:

      goutils.JoinBetween

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

      【讨论】:

        【解决方案10】:

        使用内存分配统计数据对结果进行基准测试。在github查看基准代码。

        使用 strings.Builder 优化性能。

        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
        

        【讨论】:

        • 请将您在此处构建的原始测试用例归功于 @cd1。
        【解决方案11】:
        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))
        }
        

        【讨论】:

        • 请不要仅发布代码答案。请解释这段代码的作用以及为什么它是解决方案。
        【解决方案12】:

        我使用以下方法:-

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

        【讨论】:

        • 这并没有解决 OP 通过一系列迭代构建字符串的问题,使用 for 循环。
        【解决方案13】:

        这是@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")
                }
            }
        }
        

        时间安排:

        BenchmarkConcat-4                             300000          6869 ns/op
        BenchmarkBuffer-4                            1000000          1186 ns/op
        

        【讨论】:

        • 我不认为手动设置 b.N 是使用测试包的基准功能的正确方法
        • @PickBoy,请证明您的观点。为什么你认为b.N 是一个公共变量?
        • b.N 不应该在基准函数中设置。它是由 go 测试工具动态设置的。基准函数应该运行相同的测试 b.N 次,但是在您的代码(以及 @cd1 的代码)中,循环中的每个测试都是不同的测试(因为字符串的长度正在增长)
        • @PickBoy,如果你让 go 测试工具动态设置b.N,你会在不同的测试用例中得到不同长度的字符串。见comment
        • 这就是为什么你应该在 b.N 循环中添加一个固定迭代次数的内部循环,比如 10000。
        【解决方案14】:
        package main
        
        import (
          "fmt"
        )
        
        func main() {
            var str1 = "string1"
            var str2 = "string2"
            out := fmt.Sprintf("%s %s ",str1, str2)
            fmt.Println(out)
        }
        

        【讨论】:

        • 欢迎来到 Stack Overflow!花点时间阅读帮助中心的editing help。 Stack Overflow 上的格式与其他网站不同。
        • 虽然这段代码 sn-p 可以解决问题,including an explanation 确实有助于提高您的帖子质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性的 cmets 挤满你的代码,这会降低代码和解释的可读性!
        • 这根本没有回答问题。 fmt.Sprintf 是连接简单字符串时效率最差的方法。根据这个benchfmt.Sprintf 甚至比添加运算符 (+) OP 提到的效率更低。
        【解决方案15】:

        扩展 cd1 的答案: 您可以使用 append() 而不是 copy()。 append() 进行了更大的预先准备,花费了更多的内存,但节省了时间。 我在你的顶部添加了two more benchmarks。 使用

        在本地运行
        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
        

        【讨论】:

          【解决方案16】:

          我最初的建议是

          s12 := fmt.Sprint(s1,s2)
          

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

          我最初的建议是使用反射和类型切换。 See (p *pp) doPrint and (p *pp) printArg
          基本类型没有通用的 Stringer() 接口,正如我天真地认为的那样。

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

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

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

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

          【讨论】:

          • 不是内置的,效率不高。
          • 导入一个包(如 fmt)意味着它不是内置的。它在标准库中。
          • 它很慢,只是因为它对它的参数使用了反射。这是有效的。否则它的效率不亚于使用字符串连接。加入
          【解决方案17】:

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

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

          通过我的benchmark,它比复制解决方案慢 20%(每 8.1ns append 而不是 6.72ns),但仍然比使用 bytes.Buffer 快 55%。

          【讨论】:

            【解决方案18】:
            s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))
            

            【讨论】:

            • 这个解决方案非常慢,因为它使用反射,它解析格式字符串,并为[]byte(s1) 转换复制数据。将其与发布的其他解决方案进行比较,您能说出您的解决方案的一个优点吗?
            【解决方案19】:

            您可以创建一个大字节切片并使用字符串切片将短字符串的字节复制到其中。 《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 中有这么多方法可以做到这一点。
            • 在有效的 go 中,它还说这个想法非常有用,它被捕获在一个内置函数中。所以你可以用append(slice, byte...)替换你的函数,看起来。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-11-03
            • 1970-01-01
            • 2010-09-30
            • 2011-05-05
            • 2013-08-02
            • 2010-11-28
            相关资源
            最近更新 更多