在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值,请使用指向它的指针。
更多细节请点击这里查看源代码。
其他回答
2018年新增说明
从Go 1.10开始,有一个字符串。建造者类型,请看看这个答案的更多细节。
pre - 201 x的答案
@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英特尔酷睿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
结论:
CopyPreAllocate is the fastest way; AppendPreAllocate is pretty close to No.1, but it's easier to write the code. Concat has really bad performance both for speed and memory usage. Don't use it. Buffer#Write and Buffer#WriteString are basically the same in speed, contrary to what @Dani-Br said in the comment. Considering string is indeed []byte in Go, it makes sense. bytes.Buffer basically use the same solution as Copy with extra book keeping and other stuff. Copy and Append use a bootstrap size of 64, the same as bytes.Buffer Append use 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
建议:
对于OP需要的简单任务,我将使用Append或AppendPreAllocate。它足够快而且容易使用。 如果需要同时读取和写入缓冲区,则使用字节。当然是缓冲区。这就是它的设计目的。
我使用以下方法:-
package main
import (
"fmt"
"strings"
)
func main (){
concatenation:= strings.Join([]string{"a","b","c"},"") //where second parameter is a separator.
fmt.Println(concatenation) //abc
}
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))
使用内存分配统计信息的基准测试结果。在github检查基准代码。
使用字符串。构建器来优化性能。
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
新方法:
从Go 1.10开始,有一个字符串。建造者类型,请看看这个答案的更多细节。
老方法:
使用bytes包。它有一个实现io.Writer的Buffer类型。
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)时间内完成。