go io 之 Read / ReadAtLeast / ReadFull / EOF / ErrUnexpectedEOF
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 // ring 5 // reade 5 // r 1 // 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 } 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) }