... and thus does not need to worry about when it escapes into
unprovable fmt interface{} land.
Also, add some convenience methods for efficiently writing integers.
main
parent
e6b84f2159
commit
3f4a567032
@ -0,0 +1,74 @@ |
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package strbuilder defines a string builder type that allocates
|
||||
// less than the standard library's strings.Builder by using a
|
||||
// sync.Pool, so it doesn't matter if the compiler can't prove that
|
||||
// the builder doesn't escape into the fmt package, etc.
|
||||
package strbuilder |
||||
|
||||
import ( |
||||
"bytes" |
||||
"strconv" |
||||
"sync" |
||||
) |
||||
|
||||
var pool = sync.Pool{ |
||||
New: func() interface{} { return new(Builder) }, |
||||
} |
||||
|
||||
type Builder struct { |
||||
bb bytes.Buffer |
||||
scratch [20]byte // long enough for MinInt64, MaxUint64
|
||||
locked bool // in pool, not for use
|
||||
} |
||||
|
||||
// Get returns a new or reused string Builder.
|
||||
func Get() *Builder { |
||||
b := pool.Get().(*Builder) |
||||
b.bb.Reset() |
||||
b.locked = false |
||||
return b |
||||
} |
||||
|
||||
// String both returns the Builder's string, and returns the builder
|
||||
// to the pool.
|
||||
func (b *Builder) String() string { |
||||
if b.locked { |
||||
panic("String called twiced on Builder") |
||||
} |
||||
s := b.bb.String() |
||||
b.locked = true |
||||
pool.Put(b) |
||||
return s |
||||
} |
||||
|
||||
func (b *Builder) WriteByte(v byte) error { |
||||
return b.bb.WriteByte(v) |
||||
} |
||||
|
||||
func (b *Builder) WriteString(s string) (int, error) { |
||||
return b.bb.WriteString(s) |
||||
} |
||||
|
||||
func (b *Builder) Write(p []byte) (int, error) { |
||||
return b.bb.Write(p) |
||||
} |
||||
|
||||
func (b *Builder) WriteInt(v int64) { |
||||
b.Write(strconv.AppendInt(b.scratch[:0], v, 10)) |
||||
} |
||||
|
||||
func (b *Builder) WriteUint(v uint64) { |
||||
b.Write(strconv.AppendUint(b.scratch[:0], v, 10)) |
||||
} |
||||
|
||||
// Grow grows the buffer's capacity, if necessary, to guarantee space
|
||||
// for another n bytes. After Grow(n), at least n bytes can be written
|
||||
// to the buffer without another allocation. If n is negative, Grow
|
||||
// will panic. If the buffer can't grow it will panic with
|
||||
// ErrTooLarge.
|
||||
func (b *Builder) Grow(n int) { |
||||
b.bb.Grow(n) |
||||
} |
||||
@ -0,0 +1,52 @@ |
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package strbuilder |
||||
|
||||
import ( |
||||
"math" |
||||
"testing" |
||||
) |
||||
|
||||
func TestBuilder(t *testing.T) { |
||||
const want = "Hello, world 123 -456!" |
||||
bang := []byte("!") |
||||
var got string |
||||
allocs := testing.AllocsPerRun(1000, func() { |
||||
sb := Get() |
||||
sb.WriteString("Hello, world ") |
||||
sb.WriteUint(123) |
||||
sb.WriteByte(' ') |
||||
sb.WriteInt(-456) |
||||
sb.Write(bang) |
||||
got = sb.String() |
||||
}) |
||||
if got != want { |
||||
t.Errorf("got %q; want %q", got, want) |
||||
} |
||||
if allocs != 1 { |
||||
t.Errorf("allocs = %v; want 1", allocs) |
||||
} |
||||
} |
||||
|
||||
// Verifies scratch buf is large enough.
|
||||
func TestIntBounds(t *testing.T) { |
||||
const want = "-9223372036854775808 9223372036854775807 18446744073709551615" |
||||
var got string |
||||
allocs := testing.AllocsPerRun(1000, func() { |
||||
sb := Get() |
||||
sb.WriteInt(math.MinInt64) |
||||
sb.WriteByte(' ') |
||||
sb.WriteInt(math.MaxInt64) |
||||
sb.WriteByte(' ') |
||||
sb.WriteUint(math.MaxUint64) |
||||
got = sb.String() |
||||
}) |
||||
if got != want { |
||||
t.Errorf("got %q; want %q", got, want) |
||||
} |
||||
if allocs != 1 { |
||||
t.Errorf("allocs = %v; want 1", allocs) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue