New Way:
From Go 1.10 there is a strings.Builder type, please take a look at this answer for more detail.
Old Way:
Use the bytes package. It has a Buffer type which implements 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.
Answer from marketer on Stack OverflowNOTE: After discussion with this awesome subreddit, I realize I'm asking the wrong question. I don't need a string builder. I'm optmizing just for the sake of optimizing, which is wrong. So will just stick to + operator. Thank you all for the feedback !
I'm aware of strings.Builder but here is my confusion.
I need to use some string variables. My first thought was to do this:
var s strings.Builder
name := "john"
s.WriteString("hello " + name)
fmt.Println(s.String())
Dumb question, is still wrong to use + ? Or should I do this:
var s strings.Builder
name := "john"
s.WriteString("hello ")
s.WriteString(name)
fmt.Println(s.String())EDIT1: adding bechmarks.
code:
concat_test.go
package main
import (
"strings"
"testing"
)
func BenchmarkConcatAndWrite(b *testing.B) {
var s strings.Builder
name := "john"
b.ReportAllocs()
for i := 0; i < b.N; i++ {
s.Reset()
s.WriteString("hello " + name)
}
}
func BenchmarkSeparateWrites(b *testing.B) {
var s strings.Builder
name := "john"
b.ReportAllocs()
for i := 0; i < b.N; i++ {
s.Reset()
s.WriteString("hello ")
s.WriteString(name)
}
}results:
go test -bench=. goos: darwin goarch: amd64 pkg: test cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz BenchmarkConcatAndWrite-12 25422900 44.04 ns/op 16 B/op 1 allocs/op BenchmarkSeparateWrites-12 26773579 44.37 ns/op 24 B/op 2 allocs/op PASS ok test 2.518s
EDIT2: posting actual code and updated benchmark.
concat.go
package concat
import (
"fmt"
"strings"
)
type Metadata struct {
NumReplica int `json:"num_replica"`
}
type IndexData struct {
BucketId string `json:"bucket_id"`
Condition string `json:"condition"`
DatastoreId string `json:"datastore_id"`
Id string `json:"id"`
IndexKey []string `json:"index_key"`
IsPrimary bool `json:"is_primary"`
KeyspaceId string `json:"keyspace_id"`
Metadata Metadata `json:"metadata"`
Name string `json:"name"`
NamespaceId string `json:"namespace_id"`
Partition string `json:"partition"`
ScopeId string `json:"scope_id"`
State string `json:"state"`
Using string `json:"using"`
}
func ConcatAndWrite(data IndexData) string {
var indexDefinition strings.Builder
switch data.IsPrimary {
case false:
indexDefinition.WriteString("CREATE INDEX " + data.Name + " ON ")
indexDefinition.WriteString(data.BucketId + "." + data.ScopeId + "." + data.KeyspaceId)
indexDefinition.WriteString("(")
for i, ik := range data.IndexKey {
if i > 0 {
indexDefinition.WriteString(",")
}
indexDefinition.WriteString(ik)
}
indexDefinition.WriteString(")")
if data.Partition != "" {
indexDefinition.WriteString(" PARTITION BY " + data.Partition)
}
if data.Condition != "" {
indexDefinition.WriteString(" WHERE " + data.Condition)
}
case true:
indexDefinition.WriteString("CREATE PRIMARY INDEX ")
if data.Name != "#primary" {
indexDefinition.WriteString(data.Name + " ")
}
indexDefinition.WriteString("ON " + data.BucketId + "." + data.ScopeId + "." + data.KeyspaceId)
}
if data.Metadata.NumReplica > 0 {
replicas := fmt.Sprint(data.Metadata.NumReplica)
indexDefinition.WriteString(" WITH {\"num_replica\":" + replicas + "\"}")
}
return indexDefinition.String()
}
func NoConcat(data IndexData) string {
var indexDefinition strings.Builder
switch data.IsPrimary {
case false:
indexDefinition.WriteString("CREATE INDEX ")
indexDefinition.WriteString(data.Name)
indexDefinition.WriteString(" ON ")
indexDefinition.WriteString(data.BucketId)
indexDefinition.WriteString(".")
indexDefinition.WriteString(data.ScopeId)
indexDefinition.WriteString(".")
indexDefinition.WriteString(data.KeyspaceId)
indexDefinition.WriteString("(")
for i, ik := range data.IndexKey {
if i > 0 {
indexDefinition.WriteString(",")
}
indexDefinition.WriteString(ik)
}
indexDefinition.WriteString(")")
if data.Partition != "" {
indexDefinition.WriteString(" PARTITION BY ")
indexDefinition.WriteString( data.Partition)
}
if data.Condition != "" {
indexDefinition.WriteString(" WHERE ")
indexDefinition.WriteString(data.Condition)
}
case true:
indexDefinition.WriteString("CREATE PRIMARY INDEX ")
if data.Name != "#primary" {
indexDefinition.WriteString(data.Name)
indexDefinition.WriteString( " ")
}
indexDefinition.WriteString("ON ")
indexDefinition.WriteString(data.BucketId)
indexDefinition.WriteString(".")
indexDefinition.WriteString(data.ScopeId)
indexDefinition.WriteString(".")
indexDefinition.WriteString(data.KeyspaceId)
}
if data.Metadata.NumReplica > 0 {
replicas := fmt.Sprint(data.Metadata.NumReplica)
indexDefinition.WriteString(" WITH {\"num_replica\":")
indexDefinition.WriteString(replicas )
indexDefinition.WriteString("\"}")
}
return indexDefinition.String()
}
func ConcatPlusOperator(data IndexData) string {
var indexDefinition string
switch data.IsPrimary {
case false:
indexKeys := strings.Join(data.IndexKey, ",")
indexDefinition += fmt.Sprintf("CREATE INDEX %s ON %s.%s.%s(%s)", data.Name, data.BucketId, data.ScopeId, data.KeyspaceId, indexKeys)
if data.Partition != "" {
indexDefinition += fmt.Sprintf(" PARTITION BY %s",data.Partition)
}
if data.Condition != "" {
indexDefinition += fmt.Sprintf(" WHERE %s", data.Condition)
}
case true:
indexDefinition = "CREATE PRIMARY INDEX "
if data.Name != "#primary" {
indexDefinition += fmt.Sprintf("%s ", data.Name)
}
indexDefinition += fmt.Sprintf("ON %s.%s.%s", data.BucketId, data.ScopeId, data.KeyspaceId)
}
if data.Metadata.NumReplica > 0 {
indexDefinition += fmt.Sprintf(" WITH {\"num_replica\": %d \"}", data.Metadata.NumReplica)
}
return indexDefinition
}
concat_test.go
package concat
import (
"testing"
)
func BenchmarkConcatAndWrite(b *testing.B) {
m := Metadata{NumReplica: 2}
data := IndexData{
BucketId: "jobs",
Condition: "(`id` = 2)",
DatastoreId: "http://127.0.0.1:8091",
Id: "a607ef2e22e0b436",
IndexKey: []string{"country", "name", "id"},
KeyspaceId: "c2",
Metadata: m,
Name: "idx3",
NamespaceId: "default",
Partition: "HASH((meta().`id`))",
ScopeId: "s1",
State: "online",
Using: "gsi",
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
ConcatAndWrite(data)
}
}
func BenchmarkNoConcat(b *testing.B) {
m := Metadata{NumReplica: 2}
data := IndexData{
BucketId: "jobs",
Condition: "(`id` = 2)",
DatastoreId: "http://127.0.0.1:8091",
Id: "a607ef2e22e0b436",
IndexKey: []string{"country", "name", "id"},
KeyspaceId: "c2",
Metadata: m,
Name: "idx3",
NamespaceId: "default",
Partition: "HASH((meta().`id`))",
ScopeId: "s1",
State: "online",
Using: "gsi",
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
NoConcat(data)
}
}
func BenchmarkPlusOperator(b *testing.B) {
m := Metadata{NumReplica: 2}
data := IndexData{
BucketId: "jobs",
Condition: "(`id` = 2)",
DatastoreId: "http://127.0.0.1:8091",
Id: "a607ef2e22e0b436",
IndexKey: []string{"country", "name", "id"},
KeyspaceId: "c2",
Metadata: m,
Name: "idx3",
NamespaceId: "default",
Partition: "HASH((meta().`id`))",
ScopeId: "s1",
State: "online",
Using: "gsi",
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
ConcatPlusOperator(data)
}
}
benchmarks:
go test -bench=. goos: darwin goarch: amd64 cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz BenchmarkConcatAndWrite-12 2932362 404.1 ns/op 408 B/op 5 allocs/op BenchmarkNoConcat-12 4595264 258.0 ns/op 240 B/op 4 allocs/op BenchmarkPlusOperator-12 1343035 890.4 ns/op 616 B/op 15 allocs/op PASS ok _/Users/hiteshwalia/go/src/local/test/concat 5.262s
Videos
New Way:
From Go 1.10 there is a strings.Builder type, please take a look at this answer for more detail.
Old Way:
Use the bytes package. It has a Buffer type which implements 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.
In Go 1.10+ there is strings.Builder, here.
A Builder is used to efficiently build a string using Write methods. It minimizes memory copying. The zero value is ready to use.
Example
It's almost the same with 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())
}
Click to see this on the playground.
Supported Interfaces
strings.Builder's methods are being implemented with the existing interfaces in mind so that you can switch to the new Builder type easily in your code.
| Method Signature | Interface | Description |
|---|---|---|
Grow(int) |
bytes.Buffer |
Grows the buffer's capacity by the specified amount. See bytes.Buffer#Grow for more information. |
Len() int |
bytes.Buffer |
Returns the number of bytes in the buffer. See bytes.Buffer#Len for more information. |
Reset() |
bytes.Buffer |
Resets the buffer to be empty. See bytes.Buffer#Reset for more information. |
String() string |
fmt.Stringer |
Returns the contents of the buffer as a string. See fmt.Stringer for more information. |
Write([]byte) (int, error) |
io.Writer |
Writes the given bytes to the buffer. See io.Writer for more information. |
WriteByte(byte) error |
io.ByteWriter |
Writes the given byte to the buffer. See io.ByteWriter for more information. |
WriteRune(rune) (int, error) |
bufio.Writer or bytes.Buffer |
Writes the given rune to the buffer. See bufio.Writer#WriteRune or bytes.Buffer#WriteRune for more information. |
WriteString(string) (int, error) |
io.stringWriter |
Writes the given string to the buffer. See io.stringWriter for more information. |
Differences from bytes.Buffer
- It can only grow or reset.
- It has a
copyCheckmechanism built-in that prevents accidentally copying it. Inbytes.Buffer, one can access the underlying bytes like this:(*Buffer).Bytes().strings.Builderprevents this problem. Sometimes, this is not a problem, though, and is desired instead. For example: For the peeking behavior when the bytes are passed to anio.Readeretc. bytes.Buffer.Reset()rewinds and reuses the underlying buffer whereas thestrings.Builder.Reset()does not, it detaches the buffer.
Note
- Do not copy a
strings.Buildervalue as it caches the underlying data. - If you want to share a
strings.Buildervalue, use a pointer to it.
Check out its source code for more details, here.