Definition
type Builder struct {
// contains filtered or unexported fields
}
A Builder is used to efficiently build a string using Write methods. It minimizes memory copying. The zero value is ready to use. Do not copy a non-zero Builder.
Example
package main
import (
"fmt"
"strings"
)
func main() {
var b strings.Builder
for i := 3; i >= 1; i-- {
fmt.Fprintf(&b, "%d...", i)
}
b.WriteString("ignition")
fmt.Println(b.String())
}
Analyse
// /usr/local/Cellar/go/1.16.6/libexec/src/fmt/print.go
// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
// pp is used to store a printer's state and is reused with sync.Pool to avoid allocations.
type pp struct {
buf buffer
// arg holds the current item, as an interface{}.
arg interface{}
// value is used instead of arg for reflect values.
value reflect.Value
// fmt is used to format basic items such as integers or strings.
fmt fmt
// reordered records whether the format string used argument reordering.
reordered bool
// goodArgNum records whether the most recent reordering directive was valid.
goodArgNum bool
// panicking is set by catchPanic to avoid infinite panic, recover, panic, ... recursion.
panicking bool
// erroring is set when printing an error string to guard against calling handleMethods.
erroring bool
// wrapErrs is set when the format string may contain a %w verb.
wrapErrs bool
// wrappedErr records the target of the %w verb.
wrappedErr error
}
var ppFree = sync.Pool{
New: func() interface{} { return new(pp) },
}
// newPrinter allocates a new pp struct or grabs a cached one.
func newPrinter() *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.wrapErrs = false
p.fmt.init(&p.buf)
return p
}
func (p *pp) doPrintf(format string, a []interface{}) {
// Write into pp.buf which is a buffer ([]byte)
...
}
// /usr/local/Cellar/go/1.16.6/libexec/src/strings/builder.go
// Write appends the contents of p to b's buffer.
// Write always returns len(p), nil.
func (b *Builder) Write(p []byte) (int, error) {
b.copyCheck()
b.buf = append(b.buf, p...)
return len(p), nil
}
func (b *Builder) copyCheck() {
if b.addr == nil {
// This hack works around a failing of Go's escape analysis
// that was causing b to escape and be heap allocated.
// See issue 23382.
// TODO: once issue 7921 is fixed, this should be reverted to
// just "b.addr = b".
b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
} else if b.addr != b {
panic("strings: illegal use of non-zero Builder copied by value")
}
}
Implementation
// Write appends the contents of p to b's buffer.
// Write always returns len(p), nil.
func (b *Builder) Write(p []byte) (int, error) {
b.copyCheck()
b.buf = append(b.buf, p...)
return len(p), nil
}
// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *Builder) WriteString(s string) (int, error) {
b.copyCheck()
b.buf = append(b.buf, s...)
return len(s), nil
}
Evaluation
In Go (Golang), both string and strings.Builder are used for handling string data, but they serve different purposes and are optimal for different use cases.
String (Immutable)
In Go, strings are immutable, meaning once a string is created, its value cannot be changed. If you perform any operations like concatenation, a new string is created in memory. This immutability makes strings inefficient for scenarios that involve frequent concatenation because each modification results in memory allocation for a new string, which could lead to performance overhead, especially in loops.
Example:
s := "Hello"
s += " World"
Each concatenation creates a new string in memory, which is costly for large or repetitive operations.
strings.Builder (Efficient for Concatenation)
The strings.Builder type, introduced in Go 1.10, is optimized for efficient string building and concatenation. It internally maintains a dynamic buffer, avoiding the overhead of repeatedly creating new string objects during concatenation. This makes it highly efficient for scenarios where multiple concatenations are involved.
Key features of strings.Builder:
- Mutable: Unlike strings,
Buildercan be appended to without reallocating memory for each operation. - Efficient memory usage: Since it grows the underlying buffer as needed, it reduces the number of allocations.
- Write operations: You can append to a
Builderusing methods likeWriteString()orWriteRune().
Example:
var builder strings.Builder
builder.WriteString("Hello")
builder.WriteString(" World")
result := builder.String() // Converts the buffer to a string
In this case, memory allocation is minimized, making it significantly faster than concatenating strings in a loop.
Performance Comparison
- When to use
string: If you only need to create or modify a string once or perform very few concatenations,stringis fine. Its immutability guarantees safe concurrent access. - When to use
strings.Builder: For scenarios involving multiple string concatenations, such as building output in loops or working with large dynamic strings,strings.Builderoffers much better performance.
Conclusion
string: Suitable for small-scale or single concatenations due to immutability.strings.Builder: Ideal for repetitive or large-scale string concatenations due to mutable buffer management.
For performance-intensive tasks, especially in loops, strings.Builder is the better choice.