In Go, a string
is a primitive type, which means it is read-only, and every manipulation of it will create a new string.
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.
strings.Join()
from the "strings" packageIf you have a type mismatch(like if you are trying to join an int and a string), you do RANDOMTYPE (thing you want to change)
EX:
Beginning with Go 1.10 there is a
strings.Builder
, here.Usage:
It's almost the same with
bytes.Buffer
.StringBuilder methods and interfaces it supports:
Its methods are being implemented with the existing interfaces in mind so that you can switch to the new Builder easily in your code.
Zero value usage:
Differences from bytes.Buffer:
It can only grow or reset.
In
bytes.Buffer
underlying bytes can escape like this:(*Buffer).Bytes()
;strings.Builder
prevents this problem.It also has a copyCheck mechanism inside which prevents accidentially copying it (
func (b *Builder) copyCheck() { ... }
).Check out its source code here.
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
.This took 0.81 seconds, whereas the following code:
only took 0.61 seconds. This is probably due to the overhead of creating the new
BufferString
.Update: I also benchmarked the
join
function and it ran in 0.54 seconds.Update 2018-04-03
As of Go 1.10,
string.Builder
is recommended to be a replacement forbytes.Buffer
. Check 1.10 release notes============================================================
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.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:Environment is OS X 10.11.6, 2.2 GHz Intel Core i7
Test results:
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.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.BufferSuggestion:
Append
orAppendPreAllocate
. It's fast enough and easy to use.bytes.Buffer
of course. That's what it's designed for.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":
Then when the operations are finished, use
string ( )
on the big slice of bytes to convert it into a string again.