string 如何在go中有效地连接字符串
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1760757/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to efficiently concatenate strings in go
提问by Randy Sugianto 'Yuku'
In Go, a string
is a primitive type, which means it is read-only, and every manipulation of it will create a new string.
在 Go 中, astring
是一个原始类型,这意味着它是只读的,每次对其进行操作都会创建一个新字符串。
So if I want to concatenate strings many times without knowing the length of the resulting string, what's the best way to do it?
因此,如果我想在不知道结果字符串长度的情况下多次连接字符串,那么最好的方法是什么?
The naive way would be:
天真的方法是:
s := ""
for i := 0; i < 1000; i++ {
s += getShortStringFromSomewhere()
}
return s
but that does not seem very efficient.
但这似乎不是很有效。
回答by marketer
New Way:
新方法:
From Go 1.10 there is a strings.Builder
type, please take a look at this answer for more detail.
从 Go 1.10 开始,有一种strings.Builder
类型,请查看此答案以获取更多详细信息。
Old Way:
旧方式:
Use the bytes
package. It has a Buffer
type which implements io.Writer
.
使用bytes
包。它有一个Buffer
实现io.Writer
.
package main
import (
"bytes"
"fmt"
)
func main() {
var buffer bytes.Buffer
for i := 0; i < 1000; i++ {
buffer.WriteString("a")
}
fmt.Println(buffer.String())
}
This does it in O(n) time.
这是在 O(n) 时间内完成的。
回答by cd1
The most efficient way to concatenate strings is using the builtin function copy
. In my tests, that approach is ~3x faster than using bytes.Buffer
and much much faster (~12,000x) than using the operator +
. Also, it uses less memory.
连接字符串的最有效方法是使用内置函数copy
。在我的测试中,这种方法比 using 快 3 倍,比使用bytes.Buffer
operator 快得多(~12,000 倍)+
。此外,它使用更少的内存。
I've created a test caseto prove this and here are the results:
我创建了一个测试用例来证明这一点,结果如下:
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
Below is code for testing:
下面是测试代码:
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)
}
}
回答by Inanc Gumus
Beginning with Go 1.10 there is a strings.Builder
, here.
从 Go 1.10 开始strings.Builder
,这里有一个, 。
A Builder is used to efficiently build a string using Write methods. It minimizes memory copying. The zero value is ready to use.
Builder 用于使用 Write 方法有效地构建字符串。它最大限度地减少了内存复制。零值即可使用。
Usage:
用法:
It's almost the same with bytes.Buffer
.
与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())
}
Note:Do not copy a StringBuilder value as it caches the underlying data. If you want to share a StringBuilder value, use pointers.
注意:不要复制 StringBuilder 值,因为它会缓存底层数据。如果要共享 StringBuilder 值,请使用指针。
StringBuilder methods and interfaces it supports:
它支持的 StringBuilder 方法和接口:
Its methods are being implemented with the existing interfaces in mind so that you can switch to the new Builder easily in your code.
它的方法是在考虑现有接口的情况下实现的,因此您可以在代码中轻松切换到新的 Builder。
- Grow(int) -> bytes.Buffer#Grow
- Len() int -> bytes.Buffer#Len
- Reset() -> bytes.Buffer#Reset
- String() string -> fmt.Stringer
- Write([]byte) (int, error) -> io.Writer
- WriteByte(byte) error -> io.ByteWriter
- WriteRune(rune) (int, error) -> bufio.Writer#WriteRune- bytes.Buffer#WriteRune
- WriteString(string) (int, error) -> io.stringWriter
- Grow(int) -> bytes.Buffer#Grow
- Len() int -> bytes.Buffer#Len
- Reset() -> bytes.Buffer#Reset
- String() 字符串-> fmt.Stringer
- Write([]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
Zero value usage:
零值使用:
var buf strings.Builder
Differences from bytes.Buffer:
与 bytes.Buffer 的区别:
It can only grow or reset.
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 peeking behavior when the bytes are passed to anio.Reader
etc).It also has a copyCheck mechanism built-in that prevents accidentially copying it (
func (b *Builder) copyCheck() { ... }
).
它只能增长或重置。
在
bytes.Buffer
,一个可以访问这样的底层字节:(*Buffer).Bytes()
;strings.Builder
防止这个问题。有时,这不是问题,而是需要的(例如,当字节传递给io.Reader
等时的窥视行为)。它还具有内置的 copyCheck 机制,可防止意外复制它 (
func (b *Builder) copyCheck() { ... }
)。
Check out its source code here.
在此处查看其源代码。
回答by mbarkhau
There is a library function in the strings package called Join
:
http://golang.org/pkg/strings/#Join
字符串包中有一个库函数叫做Join
:http:
//golang.org/pkg/strings/#Join
A look at the code of Join
shows a similar approach to Append function Kinopiko wrote: https://golang.org/src/strings/strings.go#L420
看一下代码Join
显示了类似于 Kinopiko 所写的 Append 函数的方法:https://golang.org/src/strings/strings.go#L420
Usage:
用法:
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
回答by JasonMc
I just benchmarked the top answer posted above in my own code (a recursive tree walk) and the simple concat operator is actually faster than the BufferString
.
我只是在我自己的代码(递归树遍历)中对上面发布的最佳答案进行了基准测试,并且简单的 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()
}
This took 0.81 seconds, whereas the following code:
这花了 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
}
only took 0.61 seconds. This is probably due to the overhead of creating the new BufferString
.
只用了 0.61 秒。这可能是由于创建新的BufferString
.
Update:I also benchmarked the join
function and it ran in 0.54 seconds.
更新:我还对该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,"")
}
回答by rog
This is the fastest solution that does not require you to know or calculate the overall buffer size first:
这是最快的解决方案,不需要您首先知道或计算整体缓冲区大小:
var data []byte
for i := 0; i < 1000; i++ {
data = append(data, getShortStringFromSomewhere()...)
}
return string(data)
By my benchmark, it's 20% slower than the copy solution (8.1ns per append rather than 6.72ns) but still 55% faster than using bytes.Buffer.
根据我的基准,它比复制解决方案慢 20%(每次追加 8.1ns 而不是 6.72ns),但仍然比使用 bytes.Buffer 快 55%。
回答by rog
You could create a big slice of bytes and copy the bytes of the short strings into it using string slices. There is a function given in "Effective Go":
您可以创建一大块字节并使用字符串切片将短字符串的字节复制到其中。“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;
}
Then when the operations are finished, use string ( )
on the big slice of bytes to convert it into a string again.
然后当操作完成时,使用string ( )
大字节切片再次将其转换为字符串。
回答by harold ramos
package main
import (
"fmt"
)
func main() {
var str1 = "string1"
var str2 = "string2"
out := fmt.Sprintf("%s %s ",str1, str2)
fmt.Println(out)
}
回答by PickBoy
Note added in 2018
2018 年添加的注释
From Go 1.10 there is a strings.Builder
type, please take a look at this answer for more detail.
从 Go 1.10 开始,有一种strings.Builder
类型,请查看此答案以获取更多详细信息。
Pre-201x answer
201x 之前的答案
The benchmark code of @cd1 and other answers are wrong. b.N
is not supposed to be set in benchmark function. It's set by the go test tool dynamically to determine if the execution time of the test is stable.
@cd1 和其他答案的基准代码是错误的。b.N
不应在基准功能中设置。由 go test 工具动态设置,以确定测试的执行时间是否稳定。
A benchmark function should run the same test b.N
times and the test inside the loop should be the same for each iteration. So I fix it by adding an inner loop. I also add benchmarks for some other solutions:
基准函数应该运行相同的测试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)
}
}
Environment is OS X 10.11.6, 2.2 GHz Intel Core i7
环境为 OS X 10.11.6, 2.2 GHz Intel Core i7
Test results:
检测结果:
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
Conclusion:
结论:
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
andBuffer#WriteString
are basically the same in speed, contrary to what @Dani-Br said in the comment. Consideringstring
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
andAppend
use a bootstrap size of 64, the same as bytes.BufferAppend
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
CopyPreAllocate
是最快的方式;AppendPreAllocate
非常接近No.1,但更容易编写代码。Concat
在速度和内存使用方面都有非常糟糕的表现。不要使用它。Buffer#Write
并且Buffer#WriteString
速度基本相同,与@Dani-Br 在评论中所说的相反。考虑到string
确实[]byte
在 Go 中,这是有道理的。- bytes.Buffer 基本上使用
Copy
与额外簿记和其他东西相同的解决方案。 Copy
并Append
使用 64 的引导程序大小,与 bytes.Buffer 相同Append
使用更多的内存和分配,我认为这与它使用的增长算法有关。它的内存增长速度不如 bytes.Buffer
Suggestion:
建议:
- For simple task such as what OP wants, I would use
Append
orAppendPreAllocate
. It's fast enough and easy to use. - If need to read and write the buffer at the same time, use
bytes.Buffer
of course. That's what it's designed for.
- 对于简单的任务,例如 OP 想要什么,我会使用
Append
或AppendPreAllocate
。它足够快且易于使用。 - 如果需要同时读写缓冲区
bytes.Buffer
,当然使用。这就是它的设计目的。
回答by Peter Buchmann
My original suggestion was
我最初的建议是
s12 := fmt.Sprint(s1,s2)
But above answer using bytes.Buffer - WriteString()is the most efficient way.
但是上面使用bytes.Buffer - WriteString() 的答案是最有效的方法。
My initial suggestion uses reflection and a type switch. See (p *pp) doPrint
and (p *pp) printArg
There is no universal Stringer() interface for basic types, as I had naively thought.
我最初的建议使用反射和类型开关。请参阅(p *pp) doPrint
和(p *pp) printArg
我天真地认为,基本类型没有通用的 Stringer() 接口。
At least though, Sprint() internallyuses a bytes.Buffer. Thus
至少,Sprint() 在内部使用了一个 bytes.Buffer。因此
`s12 := fmt.Sprint(s1,s2,s3,s4,...,s1000)`
is acceptable in terms of memory allocations.
在内存分配方面是可以接受的。
=> Sprint() concatenation can be used for quick debug output.
=> Otherwise use bytes.Buffer ... WriteString
=> Sprint() 串联可用于快速调试输出。
=> 否则使用 bytes.Buffer ... WriteString