go io 之 Read / ReadAtLeast / ReadFull / EOF / ErrUnexpectedEOF
source link: https://www.tuicool.com/articles/7v6Z7z2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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) }
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK