Skip to content

Commit ee2ecc4

Browse files
committed
bufio: add Reader.Discard
Reader.Discard is the complement to Peek. It discards the next n bytes of input. We already have Reader.Buffered to see how many bytes of data are sitting available in memory, and Reader.Peek to get that that buffer directly. But once you're done with the Peek'd data, you can't get rid of it, other than Reading it. Both Read and io.CopyN(ioutil.Discard, bufReader, N) are relatively slow. People instead resort to multiple blind ReadByte calls, just to advance the internal b.r variable. I've wanted this previously, several people have asked for it in the past on golang-nuts/dev, and somebody just asked me for it again in a private email. There are a few places in the standard library we'd use it too. Change-Id: I85dfad47704a58bd42f6867adbc9e4e1792bc3b0 Reviewed-on: https://go-review.googlesource.com/2260 Reviewed-by: Russ Cox <rsc@golang.org>
1 parent 5f179c7 commit ee2ecc4

File tree

3 files changed

+180
-2
lines changed

3 files changed

+180
-2
lines changed

src/bufio/bufio.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,39 @@ func (b *Reader) Peek(n int) ([]byte, error) {
144144
return b.buf[b.r : b.r+n], err
145145
}
146146

147+
// Discard skips the next n bytes, returning the number of bytes discarded.
148+
//
149+
// If Discard skips fewer than n bytes, it also returns an error.
150+
// If 0 <= n <= b.Buffered(), Discard is guaranteed to succeed without
151+
// reading from the underlying io.Reader.
152+
func (b *Reader) Discard(n int) (discarded int, err error) {
153+
if n < 0 {
154+
return 0, ErrNegativeCount
155+
}
156+
if n == 0 {
157+
return
158+
}
159+
remain := n
160+
for {
161+
skip := b.Buffered()
162+
if skip == 0 {
163+
b.fill()
164+
skip = b.Buffered()
165+
}
166+
if skip > remain {
167+
skip = remain
168+
}
169+
b.r += skip
170+
remain -= skip
171+
if remain == 0 {
172+
return n, nil
173+
}
174+
if b.err != nil {
175+
return n - remain, b.readErr()
176+
}
177+
}
178+
}
179+
147180
// Read reads data into p.
148181
// It returns the number of bytes read into p.
149182
// It calls Read at most once on the underlying Reader,

src/bufio/bufio_test.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,6 +1268,135 @@ func TestWriterReset(t *testing.T) {
12681268
}
12691269
}
12701270

1271+
func TestReaderDiscard(t *testing.T) {
1272+
tests := []struct {
1273+
name string
1274+
r io.Reader
1275+
bufSize int // 0 means 16
1276+
peekSize int
1277+
1278+
n int // input to Discard
1279+
1280+
want int // from Discard
1281+
wantErr error // from Discard
1282+
1283+
wantBuffered int
1284+
}{
1285+
{
1286+
name: "normal case",
1287+
r: strings.NewReader("abcdefghijklmnopqrstuvwxyz"),
1288+
peekSize: 16,
1289+
n: 6,
1290+
want: 6,
1291+
wantBuffered: 10,
1292+
},
1293+
{
1294+
name: "discard causing read",
1295+
r: strings.NewReader("abcdefghijklmnopqrstuvwxyz"),
1296+
n: 6,
1297+
want: 6,
1298+
wantBuffered: 10,
1299+
},
1300+
{
1301+
name: "discard all without peek",
1302+
r: strings.NewReader("abcdefghijklmnopqrstuvwxyz"),
1303+
n: 26,
1304+
want: 26,
1305+
wantBuffered: 0,
1306+
},
1307+
{
1308+
name: "discard more than end",
1309+
r: strings.NewReader("abcdefghijklmnopqrstuvwxyz"),
1310+
n: 27,
1311+
want: 26,
1312+
wantErr: io.EOF,
1313+
wantBuffered: 0,
1314+
},
1315+
// Any error from filling shouldn't show up until we
1316+
// get past the valid bytes. Here we return we return 5 valid bytes at the same time
1317+
// as an error, but test that we don't see the error from Discard.
1318+
{
1319+
name: "fill error, discard less",
1320+
r: newScriptedReader(func(p []byte) (n int, err error) {
1321+
if len(p) < 5 {
1322+
panic("unexpected small read")
1323+
}
1324+
return 5, errors.New("5-then-error")
1325+
}),
1326+
n: 4,
1327+
want: 4,
1328+
wantErr: nil,
1329+
wantBuffered: 1,
1330+
},
1331+
{
1332+
name: "fill error, discard equal",
1333+
r: newScriptedReader(func(p []byte) (n int, err error) {
1334+
if len(p) < 5 {
1335+
panic("unexpected small read")
1336+
}
1337+
return 5, errors.New("5-then-error")
1338+
}),
1339+
n: 5,
1340+
want: 5,
1341+
wantErr: nil,
1342+
wantBuffered: 0,
1343+
},
1344+
{
1345+
name: "fill error, discard more",
1346+
r: newScriptedReader(func(p []byte) (n int, err error) {
1347+
if len(p) < 5 {
1348+
panic("unexpected small read")
1349+
}
1350+
return 5, errors.New("5-then-error")
1351+
}),
1352+
n: 6,
1353+
want: 5,
1354+
wantErr: errors.New("5-then-error"),
1355+
wantBuffered: 0,
1356+
},
1357+
// Discard of 0 shouldn't cause a read:
1358+
{
1359+
name: "discard zero",
1360+
r: newScriptedReader(), // will panic on Read
1361+
n: 0,
1362+
want: 0,
1363+
wantErr: nil,
1364+
wantBuffered: 0,
1365+
},
1366+
{
1367+
name: "discard negative",
1368+
r: newScriptedReader(), // will panic on Read
1369+
n: -1,
1370+
want: 0,
1371+
wantErr: ErrNegativeCount,
1372+
wantBuffered: 0,
1373+
},
1374+
}
1375+
for _, tt := range tests {
1376+
br := NewReaderSize(tt.r, tt.bufSize)
1377+
if tt.peekSize > 0 {
1378+
peekBuf, err := br.Peek(tt.peekSize)
1379+
if err != nil {
1380+
t.Errorf("%s: Peek(%d): %v", tt.name, tt.peekSize, err)
1381+
continue
1382+
}
1383+
if len(peekBuf) != tt.peekSize {
1384+
t.Errorf("%s: len(Peek(%d)) = %v; want %v", tt.name, tt.peekSize, len(peekBuf), tt.peekSize)
1385+
continue
1386+
}
1387+
}
1388+
discarded, err := br.Discard(tt.n)
1389+
if ge, we := fmt.Sprint(err), fmt.Sprint(tt.wantErr); discarded != tt.want || ge != we {
1390+
t.Errorf("%s: Discard(%d) = (%v, %v); want (%v, %v)", tt.name, tt.n, discarded, ge, tt.want, we)
1391+
continue
1392+
}
1393+
if bn := br.Buffered(); bn != tt.wantBuffered {
1394+
t.Errorf("%s: after Discard, Buffered = %d; want %d", tt.name, bn, tt.wantBuffered)
1395+
}
1396+
}
1397+
1398+
}
1399+
12711400
// An onlyReader only implements io.Reader, no matter what other methods the underlying implementation may have.
12721401
type onlyReader struct {
12731402
io.Reader
@@ -1278,6 +1407,23 @@ type onlyWriter struct {
12781407
io.Writer
12791408
}
12801409

1410+
// A scriptedReader is an io.Reader that executes its steps sequentially.
1411+
type scriptedReader []func(p []byte) (n int, err error)
1412+
1413+
func (sr *scriptedReader) Read(p []byte) (n int, err error) {
1414+
if len(*sr) == 0 {
1415+
panic("too many Read calls on scripted Reader. No steps remain.")
1416+
}
1417+
step := (*sr)[0]
1418+
*sr = (*sr)[1:]
1419+
return step(p)
1420+
}
1421+
1422+
func newScriptedReader(steps ...func(p []byte) (n int, err error)) io.Reader {
1423+
sr := scriptedReader(steps)
1424+
return &sr
1425+
}
1426+
12811427
func BenchmarkReaderCopyOptimal(b *testing.B) {
12821428
// Optimal case is where the underlying reader implements io.WriterTo
12831429
srcBuf := bytes.NewBuffer(make([]byte, 8192))

src/net/http/transfer.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -638,8 +638,7 @@ func (b *body) readTrailer() error {
638638
// The common case, since nobody uses trailers.
639639
buf, err := b.r.Peek(2)
640640
if bytes.Equal(buf, singleCRLF) {
641-
b.r.ReadByte()
642-
b.r.ReadByte()
641+
b.r.Discard(2)
643642
return nil
644643
}
645644
if len(buf) < 2 {

0 commit comments

Comments
 (0)