"bytes"
"errors"
"io"
+ "strings"
"unicode/utf8"
)
return
}
-// ReadBytes reads until the first occurrence of delim in the input,
-// returning a slice containing the data up to and including the delimiter.
-// If ReadBytes encounters an error before finding a delimiter,
-// it returns the data read before the error and the error itself (often io.EOF).
-// ReadBytes returns err != nil if and only if the returned data does not end in
-// delim.
-// For simple uses, a Scanner may be more convenient.
-func (b *Reader) ReadBytes(delim byte) ([]byte, error) {
- // Use ReadSlice to look for array,
- // accumulating full buffers.
+// collectFragments reads until the first occurrence of delim in the input. It
+// returns (slice of full buffers, remaining bytes before delim, total number
+// of bytes in the combined first two elements, error).
+// The complete result is equal to
+// `bytes.Join(append(fullBuffers, finalFragment), nil)`, which has a
+// length of `totalLen`. The result is strucured in this way to allow callers
+// to minimize allocations and copies.
+func (b *Reader) collectFragments(delim byte) (fullBuffers [][]byte, finalFragment []byte, totalLen int, err error) {
var frag []byte
- var full [][]byte
- var err error
- n := 0
+ // Use ReadSlice to look for delim, accumulating full buffers.
for {
var e error
frag, e = b.ReadSlice(delim)
// Make a copy of the buffer.
buf := make([]byte, len(frag))
copy(buf, frag)
- full = append(full, buf)
- n += len(buf)
+ fullBuffers = append(fullBuffers, buf)
+ totalLen += len(buf)
}
- n += len(frag)
+ totalLen += len(frag)
+ return fullBuffers, frag, totalLen, err
+}
+// ReadBytes reads until the first occurrence of delim in the input,
+// returning a slice containing the data up to and including the delimiter.
+// If ReadBytes encounters an error before finding a delimiter,
+// it returns the data read before the error and the error itself (often io.EOF).
+// ReadBytes returns err != nil if and only if the returned data does not end in
+// delim.
+// For simple uses, a Scanner may be more convenient.
+func (b *Reader) ReadBytes(delim byte) ([]byte, error) {
+ full, frag, n, err := b.collectFragments(delim)
// Allocate new buffer to hold the full pieces and the fragment.
buf := make([]byte, n)
n = 0
// delim.
// For simple uses, a Scanner may be more convenient.
func (b *Reader) ReadString(delim byte) (string, error) {
- bytes, err := b.ReadBytes(delim)
- return string(bytes), err
+ full, frag, n, err := b.collectFragments(delim)
+ // Allocate new buffer to hold the full pieces and the fragment.
+ var buf strings.Builder
+ buf.Grow(n)
+ // Copy full pieces and fragment in.
+ for _, fb := range full {
+ buf.Write(fb)
+ }
+ buf.Write(frag)
+ return buf.String(), err
}
// WriteTo implements io.WriterTo.
}
}
+func TestReadStringAllocs(t *testing.T) {
+ r := strings.NewReader(" foo foo 42 42 42 42 42 42 42 42 4.2 4.2 4.2 4.2\n")
+ buf := NewReader(r)
+ allocs := testing.AllocsPerRun(100, func() {
+ r.Seek(0, io.SeekStart)
+ buf.Reset(r)
+
+ _, err := buf.ReadString('\n')
+ if err != nil {
+ t.Fatal(err)
+ }
+ })
+ if allocs != 1 {
+ t.Errorf("Unexpected number of allocations, got %f, want 1", allocs)
+ }
+}
+
func TestWriter(t *testing.T) {
var data [8192]byte
}
}
+func BenchmarkReaderReadString(b *testing.B) {
+ r := strings.NewReader(" foo foo 42 42 42 42 42 42 42 42 4.2 4.2 4.2 4.2\n")
+ buf := NewReader(r)
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ r.Seek(0, io.SeekStart)
+ buf.Reset(r)
+
+ _, err := buf.ReadString('\n')
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
func BenchmarkWriterCopyOptimal(b *testing.B) {
// Optimal case is where the underlying writer implements io.ReaderFrom
srcBuf := bytes.NewBuffer(make([]byte, 8192))