This may be a duplicate but I haven't been able to find the correct answer anywhere. How do you prepend to a string in GoLang without using +
operator (which is considered slow)?
I know I can append to a string using bytes.Buffer
but WriteString
only appends. If I want to prepend, I'll have to write to the prefix with the string of suffix like so:
package main
import (
"bytes"
"fmt"
)
func main() {
var b bytes.Buffer
b.WriteString("W")
b.WriteString("o")
b.WriteString("r")
b.WriteString("l")
b.WriteString("d")
var a bytes.Buffer
a.WriteString("Hello ")
a.WriteString(b.String())
fmt.Println(a.String())
}
Is there a better way?
Using the strings.Builder
is the way to go, if it is on a performance critical path, otherwise a simple +
is more than good; should you know the length beforehand you may use the array or slice and copy too (Thanks to @mh-cbon, and see slice-allocation-performance):
go test -benchtime=4731808x -benchmem -bench .
BenchmarkBufferArraySimplified-8 4731808 11.36 ns/op 0 B/op 0 allocs/op
BenchmarkBufferArray-8 4731808 62.19 ns/op 0 B/op 0 allocs/op
BenchmarkBuilder-8 4731808 140.7 ns/op 32 B/op 3 allocs/op
BenchmarkBuffer-8 4731808 200.7 ns/op 128 B/op 2 allocs/op
BenchmarkByteAppend-8 4731808 223.2 ns/op 16 B/op 4 allocs/op
BenchmarkPlus-8 4731808 226.2 ns/op 16 B/op 4 allocs/op
BenchmarkByteCopy-8 4731808 254.1 ns/op 32 B/op 5 allocs/op
BenchmarkPrepend-8 4731808 273.7 ns/op 24 B/op 5 allocs/op
Benchmark:
package main
import (
"bytes"
"strings"
"testing"
)
func BenchmarkBufferArraySimplified(b *testing.B) {
for i := 0; i < b.N; i++ {
var y [11]byte // should you know the length beforehand
y[6] = 'W'
y[7] = 'o'
y[8] = 'r'
y[9] = 'l'
y[10] = 'd'
copy(y[0:], "Hello ")
_ = string(y[:])
}
}
func BenchmarkBufferArray(b *testing.B) {
for i := 0; i < b.N; i++ {
var y [11]byte
hello := "Hello "
n := len(hello) // should you know the length beforehand
b := bytes.NewBuffer(y[:n])
b.WriteString("W")
b.WriteString("o")
b.WriteString("r")
b.WriteString("l")
b.WriteString("d")
a := bytes.NewBuffer(y[:0])
a.WriteString(hello) // prepend
_ = b.String()
}
}
func BenchmarkBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
var b strings.Builder
b.WriteString("W")
b.WriteString("o")
b.WriteString("r")
b.WriteString("l")
b.WriteString("d")
var a strings.Builder
a.WriteString("Hello ") // prepend
a.WriteString(b.String())
_ = a.String()
}
}
func BenchmarkBuffer(b *testing.B) {
for i := 0; i < b.N; i++ {
var b bytes.Buffer
b.WriteString("W")
b.WriteString("o")
b.WriteString("r")
b.WriteString("l")
b.WriteString("d")
var a bytes.Buffer
a.WriteString("Hello ") // prepend
a.WriteString(b.String())
_ = a.String()
}
}
func BenchmarkByteAppend(b *testing.B) {
for i := 0; i < b.N; i++ {
b := "W"
b += "o"
b += "r"
b += "l"
b += "d"
_ = ByteAppend("Hello ", b) // prepend
}
}
func ByteAppend(a, b string) string {
return string(append([]byte(a), []byte(b)...)) // a+b
}
func BenchmarkPlus(b *testing.B) {
for i := 0; i < b.N; i++ {
b := "W"
b += "o"
b += "r"
b += "l"
b += "d"
_ = "Hello " + b // prepend
}
}
func BenchmarkByteCopy(b *testing.B) {
for i := 0; i < b.N; i++ {
b := "W"
b += "o"
b += "r"
b += "l"
b += "d"
_ = byteCopy("Hello ", b) // prepend
}
}
func byteCopy(a, b string) string {
c := make([]byte, len(a)+len(b))
copy(c, a)
copy(c[len(a):], b) // a+b
return string(c)
}
func BenchmarkPrepend(b *testing.B) {
for i := 0; i < b.N; i++ {
b := " "
b += "W"
b += "o"
b += "r"
b += "l"
b += "d"
_ = string(prepend([]byte(b), []byte("Hello"))) // prepend
}
}
// prepend: insert b into a at index 0: len(a) >= len(b)
func prepend(a, b []byte) []byte {
// if len(a) >= len(b) {
a = append(a[:len(b)], a...) // grow
copy(a, b)
return a
// }
// return append(b, a...)
}