在Go中,字符串是一种基本类型,这意味着它是只读的,对它的每次操作都会创建一个新字符串。
如果我想多次连接字符串而不知道结果字符串的长度,最好的方法是什么?
最天真的做法是:
var s string
for i := 0; i < 1000; i++ {
s += getShortStringFromSomewhere()
}
return s
但这似乎不是很有效。
在Go中,字符串是一种基本类型,这意味着它是只读的,对它的每次操作都会创建一个新字符串。
如果我想多次连接字符串而不知道结果字符串的长度,最好的方法是什么?
最天真的做法是:
var s string
for i := 0; i < 1000; i++ {
s += getShortStringFromSomewhere()
}
return s
但这似乎不是很有效。
当前回答
在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值,请使用指向它的指针。
更多细节请点击这里查看源代码。
其他回答
我最初的建议是
s12 := fmt.Sprint(s1,s2)
但以上答案使用字节。Buffer - WriteString()是最有效的方法。
我最初的建议是使用反射和类型开关。参见(p *pp) doPrint和(p *pp) printArg 我曾经天真地认为,基本类型没有通用的Stringer()接口。
至少Sprint()内部使用bytes.Buffer。因此
`s12 := fmt.Sprint(s1,s2,s3,s4,...,s1000)`
在内存分配方面是可接受的。
Sprint()连接可用于快速调试输出。 =>否则使用bytes。缓冲……WriteString
这是最快的解决方案,不需要 你首先需要知道或计算总的缓冲区大小:
var data []byte
for i := 0; i < 1000; i++ {
data = append(data, getShortStringFromSomewhere()...)
}
return string(data)
根据我的基准测试,它比复制解决方案慢了20% (8.1ns / 追加而不是6.72ns),但仍然比使用bytes.Buffer快55%。
简单易消化的解决方案。详情请见评论。 Copy覆盖slice的元素。我们对单个元素进行切片,然后覆盖它。
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)
}
您可以创建一个大的字节片,并使用字符串片将短字符串的字节复制到其中。在“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()将其再次转换为字符串。
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))