在Go中, string
是基本类型,这意味着它是只读的,并且对它的每次操作都将创建一个新字符串。
因此,如果我想在不知道结果字符串长度的情况下多次连接字符串,那么最好的方法是什么?
天真的方式是:
s := ""
for i := 0; i < 1000; i++ {
s += getShortStringFromSomewhere()
}
return s
但这似乎不是很有效。
#1楼
我只是在我自己的代码(递归树遍历)中对上面发布的最佳答案进行了基准测试,而简单的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,"")
}
#2楼
我原来的建议是
s12 := fmt.Sprint(s1,s2)
但上面的回答使用bytes.Buffer - WriteString()是最有效的方法。
我的初步建议使用反射和类型切换。 请参阅(p *pp) doPrint
和(p *pp) printArg
基本类型没有通用的Stringer()接口,正如我天真地想的那样。
至少,Sprint() 内部使用bytes.Buffer。 从而
`s12 := fmt.Sprint(s1,s2,s3,s4,...,s1000)`
在内存分配方面是可以接受的。
=> Sprint()连接可用于快速调试输出。
=>否则使用bytes.Buffer ... WriteString
#3楼
您可以创建一大块字节,并使用字符串切片将短字符串的字节复制到其中。 “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 ( )
将其再次转换为字符串。
#4楼
字符串包中有一个名为Join
的库函数: http : //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
#5楼
注释于2018年添加
从Go 1.10开始有一个strings.Builder
类型, 请看一下这个答案以获取更多细节 。
201x之前的答案
最好的方法是使用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)时间内完成的。
#6楼
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))
#7楼
连接字符串的最有效方法是使用内置函数copy
。 在我的测试中,这种方法比使用bytes.Buffer
快3倍,比使用operator +
快得多(~12,000x)。 此外,它使用更少的内存。
我已经创建了一个测试用例来证明这一点,结果如下:
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)
}
}
#8楼
这是最快的解决方案,不需要您首先了解或计算总体缓冲区大小:
var data []byte
for i := 0; i < 1000; i++ {
data = append(data, getShortStringFromSomewhere()...)
}
return string(data)
根据我的基准测试 ,它比复制解决方案慢20%(每次追加8.1ns而不是6.72ns),但仍然比使用bytes.Buffer快55%。
#9楼
扩展cd1的答案:您可以使用append()而不是copy()。 append()会提供更大的预付款,花费更多的内存,但节省时间。 我在你的顶部添加了两个基准测试 。 在本地运行
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
#10楼
package main
import (
"fmt"
)
func main() {
var str1 = "string1"
var str2 = "string2"
out := fmt.Sprintf("%s %s ",str1, str2)
fmt.Println(out)
}
#11楼
strings.Join()
来自“strings”包
如果您的类型不匹配(如果您尝试加入int和字符串),则执行RANDOMTYPE(您要更改的内容)
EX:
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
#12楼
这是由@ 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
#13楼
注释于2018年添加
从Go 1.10开始有一个strings.Builder
类型, 请看一下这个答案以获取更多细节 。
201x之前的答案
@ cd1和其他答案的基准代码是错误的。 bN
不应该在基准函数中设置。 它由go测试工具动态设置,以确定测试的执行时间是否稳定。
基准函数应该运行相同的测试bN
次,并且循环内的测试对于每次迭代应该是相同的。 所以我通过添加内循环来修复它。 我还为其他一些解决方案添加了基准:
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
结论:
-
CopyPreAllocate
是最快的方式;AppendPreAllocate
非常接近No.1,但编写代码更容易。 -
Concat
在速度和内存使用方面都表现不佳。 不要使用它。 -
Buffer#Write
和Buffer#WriteString
的速度基本相同,与@ Dani-Br在评论中所说的相反。 考虑到string
在Go中确实是[]byte
,这是有道理的。 - bytes.Buffer基本上使用与
Copy
相同的解决方案,具有额外的簿记和其他内容。 -
Copy
和Append
使用64的bootstrap,与bytes.Buffer相同 -
Append
使用更多的内存和分配,我认为它与它使用的增长算法有关。 它不像bytes.Buffer那样快速增长内存
建议:
- 对于OP想要的简单任务,我会使用
Append
或AppendPreAllocate
。 它足够快且易于使用。 - 如果需要同时读取和写入缓冲区,请使用
bytes.Buffer
。 这就是它的设计目标。
#14楼
从Go 1.10开始, 这里有一个strings.Builder
。
Builder用于使用Write方法高效地构建字符串。 它最小化了内存复制。 零值可以使用。
用法:
它与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())
}
注意:不要复制StringBuilder值,因为它会缓存基础数据。 如果要共享StringBuilder值,请使用指针。
它支持的StringBuilder方法和接口:
它的方法是在考虑现有接口的情况下实现的,因此您可以在代码中轻松切换到新的Builder。
- Grow(int) - > bytes.Buffer #Erow
- Len()int - > bytes.Buffer #Len
- Reset() - > bytes.Buffer #Reset
- String()string - > fmt.Stringer
- 写([] byte)(int,error) - > io.Writer
- WriteByte(byte)错误- > io.ByteWriter
- WriteRune(rune)(int,error) - > bufio.Writer#WriteRune - bytes.Buffer#WriteRune
- WriteString(string)(int,error) - > io.stringWriter
零值使用:
var buf strings.Builder
与bytes.Buffer的差异:
它只能增长或重置。
在
bytes.Buffer
,可以像下面这样访问底层字节:(*Buffer).Bytes()
;strings.Builder
可以防止这个问题。 有时,这不是一个问题,而是需要(例如,当字节传递给io.Reader
等时偷看行为)。它还内置了copyCheck机制,可以防止意外复制它(
func (b *Builder) copyCheck() { ... }
)。
在这里查看其源代码 。
#15楼
我使用以下方法做到: -
package main
import (
"fmt"
"strings"
)
func main (){
concatenation:= strings.Join([]string{"a","b","c"},"") //where second parameter is a separator.
fmt.Println(concatenation) //abc
}
#16楼
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))
}
#17楼
内存分配统计的基准测试结果。 检查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
#18楼
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()
}
来源:CSDN
作者:asdfgh0077
链接:https://blog.csdn.net/asdfgh0077/article/details/103717379