【问题标题】:How to prepend a string in GoLang without plus operation?如何在没有加号操作的情况下在 GoLang 中添加字符串?
【发布时间】:2020-12-13 07:31:44
【问题描述】:

这可能是重复的,但我无法在任何地方找到正确的答案。如何在不使用 + 运算符(被认为很慢)的情况下在 GoLang 中添加字符串?

我知道我可以使用bytes.Buffer追加到字符串,但WriteString 只能追加。如果我想预先添加,我将不得不使用后缀字符串写入前缀,如下所示:

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var b bytes.Buffer 
      
    b.WriteString("W") 
    b.WriteString("o") 
    b.WriteString("r") 
    b.WriteString("l") 
    b.WriteString("d") 
   
    var a bytes.Buffer
    a.WriteString("Hello ")
    a.WriteString(b.String())
  
    fmt.Println(a.String()) 
}

有没有更好的办法?

【问题讨论】:

  • 你测量了吗? + 在性能和清晰度方面,在许多情况下都优于替代方案。
  • 永远不要根据传闻中的微优化做出决定。编写正确、可读和可维护的代码。然后对其进行基准测试。如果性能不可接受,则对其进行分析,并优化分析器指示对性能影响最大的内容。请记住:过早的优化是万恶之源
  • BurakSerdar:不,我没有。我刚刚检查了答案(包括正确标记的答案)。我缺乏 Go 经验加上我自己的好奇心导致了这个问题。阿迪兰:谢谢你的提醒。

标签: string go stringbuilder


【解决方案1】:

使用strings.Builder 是可行的方法,如果它在性能关键路径 上,否则简单 + 非常好;如果您事先知道长度,您也可以使用数组或切片并复制(感谢@mh-cbon,请参阅slice-allocation-performance):

go test -benchtime=4731808x -benchmem -bench .
BenchmarkBufferArraySimplified-8  4731808   11.36 ns/op   0 B/op  0 allocs/op
BenchmarkBufferArray-8            4731808   62.19 ns/op   0 B/op  0 allocs/op
BenchmarkBuilder-8                4731808  140.7 ns/op   32 B/op  3 allocs/op
BenchmarkBuffer-8                 4731808  200.7 ns/op  128 B/op  2 allocs/op
BenchmarkByteAppend-8             4731808  223.2 ns/op   16 B/op  4 allocs/op
BenchmarkPlus-8                   4731808  226.2 ns/op   16 B/op  4 allocs/op
BenchmarkByteCopy-8               4731808  254.1 ns/op   32 B/op  5 allocs/op
BenchmarkPrepend-8                4731808  273.7 ns/op   24 B/op  5 allocs/op

基准测试:

package main

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

func BenchmarkBufferArraySimplified(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var y [11]byte // should you know the length beforehand
        y[6] = 'W'
        y[7] = 'o'
        y[8] = 'r'
        y[9] = 'l'
        y[10] = 'd'

        copy(y[0:], "Hello ")
        _ = string(y[:])
    }
}
func BenchmarkBufferArray(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var y [11]byte
        hello := "Hello "
        n := len(hello) // should you know the length beforehand
        b := bytes.NewBuffer(y[:n])
        b.WriteString("W")
        b.WriteString("o")
        b.WriteString("r")
        b.WriteString("l")
        b.WriteString("d")

        a := bytes.NewBuffer(y[:0])
        a.WriteString(hello) // prepend
        _ = b.String()
    }
}
func BenchmarkBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var b strings.Builder
        b.WriteString("W")
        b.WriteString("o")
        b.WriteString("r")
        b.WriteString("l")
        b.WriteString("d")

        var a strings.Builder
        a.WriteString("Hello ") // prepend
        a.WriteString(b.String())
        _ = a.String()
    }
}
func BenchmarkBuffer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var b bytes.Buffer
        b.WriteString("W")
        b.WriteString("o")
        b.WriteString("r")
        b.WriteString("l")
        b.WriteString("d")

        var a bytes.Buffer
        a.WriteString("Hello ") // prepend
        a.WriteString(b.String())
        _ = a.String()
    }
}
func BenchmarkByteAppend(b *testing.B) {
    for i := 0; i < b.N; i++ {
        b := "W"
        b += "o"
        b += "r"
        b += "l"
        b += "d"

        _ = ByteAppend("Hello ", b) // prepend
    }
}
func ByteAppend(a, b string) string {
    return string(append([]byte(a), []byte(b)...)) // a+b
}
func BenchmarkPlus(b *testing.B) {
    for i := 0; i < b.N; i++ {
        b := "W"
        b += "o"
        b += "r"
        b += "l"
        b += "d"

        _ = "Hello " + b // prepend
    }
}
func BenchmarkByteCopy(b *testing.B) {
    for i := 0; i < b.N; i++ {
        b := "W"
        b += "o"
        b += "r"
        b += "l"
        b += "d"

        _ = byteCopy("Hello ", b) // prepend
    }
}
func byteCopy(a, b string) string {
    c := make([]byte, len(a)+len(b))
    copy(c, a)
    copy(c[len(a):], b) // a+b
    return string(c)
}
func BenchmarkPrepend(b *testing.B) {
    for i := 0; i < b.N; i++ {
        b := " "
        b += "W"
        b += "o"
        b += "r"
        b += "l"
        b += "d"

        _ = string(prepend([]byte(b), []byte("Hello"))) // prepend
    }
}

// prepend: insert b into a at index 0:  len(a) >= len(b)
func prepend(a, b []byte) []byte {
    // if len(a) >= len(b) {
    a = append(a[:len(b)], a...) // grow
    copy(a, b)
    return a
    // }
    // return append(b, a...)
}

【讨论】:

  • 很高兴知道两倍快的替代方案,但这回答了这个问题。 OP 要求一种将字符串预先添加字符串到现有字符串的方法。
  • 如果你不介意线程安全,你也可以这样做play.golang.org/p/hvM_A9VIOHj
  • @КонстантинВан:查看 prepend 的新编辑。
  • @mh-cbon 感谢您的评论,请参阅新的编辑和基准:b := bytes.NewBuffer(y[:n]),注意:您可能在您的示例中使用 y[:n] 而不是 y[:0]
  • 是的,我遇到了一些使用:n 的分配,所以我粗暴地将它们全部切换到:0。不过,我想说明您可以分配一大块内存的事实,例如var y [1024 * 1024 * 5000]byte,它也可以工作。如果代码也使用 bytes.Buffer 那么它可以按需自动增长。使用内置关键字也可以实现自动增长。 play.golang.org/p/58CHh2mnn0_w
【解决方案2】:

我遇到了需要预先添加 10,000 个字符串的情况。这与预置 10 个字符串的最快方法有点不同,但这里还没有回答。

TL;DR 将 10,000 个字符串存储在 []string 中,然后使用 strings.StringBuilder 以相反的顺序组装它。

        var strs []string
        for i := 0; i < 10000; i++ {
            strs = append(strs, "Put your string here")
        }
        var b strings.Builder
        for i := len(strs) - 1; i >= 0; i -= 1 {
            b.WriteString(strs[j])
        }

+ 的幼稚方法使用了 10 GB 内存 + 1 秒。将大量时间投入到从后面填充缓冲区的自定义前置代码中,将相同的测试用例减少到 10mb + 2 毫秒。但事实证明,只需存储每个字符串,然后使用字符串生成器就可以用更少的代码实现几乎相同的结果。 (14mb 2.4 毫秒)。 以下是基准测试结果:

go test -benchtime=50x -benchmem -bench .
BenchmarkPrepend10kAddition-20                                50         971398162 ns/op        9666950277 B/op    20325 allocs/op
BenchmarkPrepend10kPrepender-20                               50           1924394 ns/op        10165544 B/op      29913 allocs/op
BenchmarkPrepend10kReorderThenStringBuilder-20                50           2436740 ns/op        14518100 B/op      19952 allocs/op

这是代码(没有 prepender 类,因为这需要更多的工作才能公开):

package main

import (
    "strconv"
    "strings"
    "testing"

    "github.com/stretchr/testify/assert"
)

var teststrings []string = []string{
    "Test test tses test test sets etsetsetsetsettesestsetet\n",
    "TEST TEST TEST TEST TEST EST SET ET SET EST EST SET E TSE TES T STE ET SETT\n",
    "ashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",    "asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
    "asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
    "asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
    "asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
    "asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
    "asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
}

func BenchmarkPrepend10kAddition(b *testing.B) {
    for i := 0; i < b.N; i++ {
        str := ""
        for j := 0; j < 10000; j++ {
            str = (strconv.Itoa(j) + teststrings[j%4]) + str
        }
        _ = string(str)
    }
}

func BenchmarkPrepend10kPrepender(b *testing.B) {
    for i := 0; i < b.N; i++ {
        // Example using custom prepender code not included in this post
        prepender := NewStringPrepender()
        for j := 0; j < 10000; j++ {
            prepender.Prepend(strconv.Itoa(j) + teststrings[j%4])
        }
        _ = string(prepender.String())
    }
}

func BenchmarkPrepend10kReorderThenStringBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        // What if we have 10,000 strings and assemble them at the end?
        var strs []string
        for j := 0; j < 10000; j++ {
            strs = append(strs, strconv.Itoa(j)+teststrings[j%4])
        }
        var b strings.Builder
        for j := len(strs) - 1; j >= 0; j -= 1 {
            b.WriteString(strs[j])
        }
        _ = string(b.String())
    }
}

为了在基准测试中生成字符串,我使用 Itoa + 测试字符串列表。这可能是不必要的,但我添加了它们以确保 []string 中的值不指向相同的内存位置。随机测试字符串会改变连接字符串的长度。

【讨论】:

    猜你喜欢
    • 2015-09-09
    • 1970-01-01
    • 2020-10-14
    • 1970-01-01
    • 2011-05-18
    • 2013-03-02
    • 2013-05-05
    • 2021-09-30
    • 1970-01-01
    相关资源
    最近更新 更多