go
的 io
包提供了ReadFull / ReadAtLeast
函数对Reader
对象进行读操作,任何实现io.Reader
接口的对象都可以使用这两个方法,同时还延伸出io.EOF / io.ErrUnexpectedEOF
错误,下面实践一下。
io.Reader Interface & io.Reader.Read
Reader
对象必须实现了io.Reader
接口,此接口约定了Read
方法,实现此方法的对象都可以使用go io
提供的其他方法进行读操作,比如 ReadAtLeast/ReadFull
,io.Reader.Read
方法的实现规则如下:
// 尝试读取 len(p) 字节的数据 并返回实际读取到的字节数 n 和 err
// 当 n > 0 时,err = nil,n <= len(p)
// 当 n = 0 时,err = EOF (内容为空 或 内容读取完)
type Reader interface {
Read(p []byte) (n int, err error)
}
io.Reader 对象
io.Reader
对象通过Read
方法将尝试读取len(p)
字节的数据放入p []byte
中,并返实际读取到的字节数 n
和 err
,err
为 nil
或 io.EOF
,具体的返回规则如下:
- 如果
n == len(p)
或0 < n < len(p)
,则err
为nil
(即至少读到了一些东西)。 - 如果
内容为空
或没有剩余未读的内容了
,则应返回io.EOF
错误。
封账一个Reader
对象
type StringReader struct {
s []byte // content
cursor int // latest read position
len int // content length
}
func NewStringReader(content string) *StringReader {
contentByte := []byte(content)
return &StringReader{s: contentByte, cursor: -1, len: len(contentByte)}
}
// StringReader 实现 io.Reader 接口的 Read 方法
func (s *StringReader) Read(p []byte) (n int, err error) {
nextIndex := s.cursor + 1
lr, lp := len(s.s[nextIndex:]), len(p)
// 游标已到内容尾部
if s.cursor == (s.len - 1) {
return 0, io.EOF
}
if lr <= lp { // 剩余可读取内容小于暂存区长度 则全量读取
n = copy(p, s.s[nextIndex:])
s.cursor = s.len - 1
return n, nil
} else { // 剩余可读取内容大于暂存区长度 则部分读取
n = copy(p, s.s[nextIndex:(nextIndex + lp + 1)])
s.cursor += lp
return lp, nil
}
}
// reset cursor
func (s *StringReader) Reset() {
s.cursor = -1
}
func main() {
// 5 bytes 的存储区
strTmp := make([]byte, 5)
// 遵循 io.Reader 接口
var myStrReader io.Reader
myStrReader = NewStringReader("my string reader")
n, err := myStrReader.Read(strTmp)
fmt.Printf("%s %d %v \n", strTmp[:n], n, err)
n, err = myStrReader.Read(strTmp)
fmt.Printf("%s %d %v \n", strTmp[:n], n, err)
n, err = myStrReader.Read(strTmp)
fmt.Printf("%s %d %v \n", strTmp[:n], n, err)
n, err = myStrReader.Read(strTmp)
fmt.Printf("%s %d %v \n", strTmp[:n], n, err)
// run result
// my st 5 <nil>
// ring 5 <nil>
// reade 5 <nil>
// r 1 <nil>
// 0 EOF
myStrReader.Reset()
n, err = io.ReadFull(myStrReader, strTmp)
fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
}
io.ReadAtLeast / io.ReadFull
go
提供了两个io
函数对Reader
对象做更强大的读取模式,其实还是围绕io.Reader.Read
方法进行的,所以如果想让自己的Reader
对象也能正确的被这两个函数使用,一定要按上文所说的准则实现。
// 断言最少读
func io.ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {
...
n, err = r.Read(buf[:])
...
}
// 断言全量读
func io.ReadFull(r Reader, buf []byte) (n int, err error) {
return ReadAtLeast(r, buf, len(buf))
}
这两个函数在操作Reader
对象读
的过程中,产生了一个新的错误态:io.ErrUnexpectedEOF
-
io.ReadAtLeast
贪婪读,至少读 min 个即视为成功,尽可能的读 len(buf)
当读取的内容字节数 n == 0 时,err = io.EOF
当 0 < n < min 时,err = io.ErrUnexpectedEOF
当 n >= min 时,err = nil -
io.ReadFull
断言读,必须读 len(buf) 才视为成功
当读取的内容字节数 n == 0 时,err = io.EOF
当 0 < n < len(buf) 时,err = io.ErrUnexpectedEOF
当 n == len(buf) 时,err = nil
func main() {
// 5 bytes 的存储区
strTmp := make([]byte, 5)
// 遵循 io.Reader 接口
var myStrReader io.Reader
// 内容 10 bytes
// 两次读取尽 第 3 次时会返回 EOF
myStrReader = NewStringReader("1234567890")
n, err = io.ReadFull(myStrReader, strTmp)
fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
n, err = io.ReadFull(myStrReader, strTmp)
fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
n, err = io.ReadFull(myStrReader, strTmp)
fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
// 内容 11 bytes
// 第3次读取时只能读取到 1 byte
// 不足 len(strTmp) 所以会返回 ErrUnexpectedEOF 此时内容已读尽
// 第4次读取会返回 EOF 错误
myStrReader = NewStringReader("12345678901")
n, err = io.ReadFull(myStrReader, strTmp)
fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
n, err = io.ReadFull(myStrReader, strTmp)
fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
n, err = io.ReadFull(myStrReader, strTmp)
fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
n, err = io.ReadFull(myStrReader, strTmp)
fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
}
io.EOF / io.ErrUnexpectedEOF
io.EOF
io.EOF
是在没有任何可读取的内容时触发,比如某文件Reader
对象,文件本身为空,或者读取若干次后,文件指针指向了末尾,调用Read
都会触发EOF
。
io.ErrUnexpectedEOF
io.ErrUnexpectedEOF
是在设定了一次读取操作时应读取到的期望字节数阈值
(ReadAtLeast min / ReadFull len(buf)
)时,读取到了n
个字节,且 0 < n < 期望字节数阈值
时,则会返回 io.ErrUnexpectedEOF
,即有内容,但不足 最小阈值字节数
,没能按期望读取足量的内容到就EOF
了,所以 ErrUnexpectedEOF
。如果没有内容了,即 n == 0
,则会返回 io.EOF
。
参考Reader 对象的 Read
方法,没有期望字节数阈值
的设定,只有在没有读取到内容0 == n
时则视为io.EOF
,否则不视为发生错误。
所以如果使用ReadAtLeast/ ReadFull
时一定要捕获io.EOF / io.ErrUnexpectedEOF
两个错误,这两个错误其实都是读取完毕的状态。
io.ReaderAtLeast 源码解读
ReaderAtLeast
将 Reader
对象的内容读取到 buf
中,并设定至少应读取 min
个字节的阈值。
- 当
len(buf) < min
,则返回io.ErrShortBuffer
,因为缓冲区装不下最小读取量啊! - 当读取的内容长度
n
不足min
时,如果n == 0
,说明Reader
对象内容为空,则返回io.EOF
错误;如果0 < n < min
,说明只读取到了部分数据,则返回io.ErrUnExpectedEOF
。 - 当且仅当
n >= min
时,返回的err
应为nil
,即便此时Reader
对象Read
内容时发生了错误,错误也应该被丢弃,为什么呢?贪婪读,当条件满足贪婪的最低条件后,后续即便发生了错误,此次贪婪读也已经被满足,所以无错,返回已经正常读取的n
字节的数据即可。
// ReadAtLeast reads from r into buf until it has read at least min bytes.
// It returns the number of bytes copied and an error if fewer bytes were read.
// The error is EOF only if no bytes were read.
// If an EOF happens after reading fewer than min bytes,
// ReadAtLeast returns ErrUnexpectedEOF.
// If min is greater than the length of buf, ReadAtLeast returns ErrShortBuffer.
// On return, n >= min if and only if err == nil.
// If r returns an error having read at least min bytes, the error is dropped.
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {
if len(buf) < min {
return 0, ErrShortBuffer
}
for n < min && err == nil {
var nn int
nn, err = r.Read(buf[n:])
n += nn
}
if n >= min {
err = nil
} else if n > 0 && err == EOF {
err = ErrUnexpectedEOF
}
return
}
e.g.直观一些
// 构造一个 Reader 对象
strReader := strings.NewReader("hello sqrtcat!")
buf := make([]byte, 6)
// "hello " n = 6 err nil
n, err := io.ReadAtLeast(strReader, buf, 4)
fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)
// "sqrtca" n = 6 err nil
n, err := io.ReadAtLeast(strReader, buf, 4)
fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)
// "t!" n = 2 err ErrUnexpectedEOF
// 这里如果把 min 4 改为 min 2 的话则满足了 n >= min 的条件 则返回的错误为 nil
n, err := io.ReadAtLeast(strReader, buf, 4)
fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)
// strReader 已为空 n = 0 err EOF
n, err := io.ReadAtLeast(strReader, buf, 4)
fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)
实际应用
strReader := strings.NewReader("hello sqrtcat!")
buf := make([]byte, 6)
for {
n, err := io.ReadAtLeast(strReader, buf, 4)
if nil != err {
// 为什么会有 ErrUnexpectedEOF 呢?
// 当数据长度 % min == 0 时不会触发 ErrUnexpectedEOF 而是在成功读取 数据长度 / min 次后下一次读取触发 EOF
// 当数据长度 % min != 0 时则会先触发 ErrUnexpectedEOF 如果继续读的话则会触发 EOF
if io.EOF == err || io.ErrUnexpectedEOF == err {
fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)
break
}
log.Panicf("read error: %s \n", err.Error())
}
fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)
}
有疑问加站长微信联系(非本文作者)