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
,具体的返回规则如下:

  1. 如果 n == len(p)
    0 < n < len(p)
    ,则 err
    nil
    (即至少读到了一些东西)。
  2. 如果 内容为空
    没有剩余未读的内容了
    ,则应返回 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

  1. io.ReadAtLeast
    贪婪读,至少读 min 个即视为成功,尽可能的读 len(buf)
    当读取的内容字节数 n == 0 时,err = io.EOF
    当 0 < n < min 时,err = io.ErrUnexpectedEOF
    当 n >= min 时,err = nil
  2. 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
个字节的阈值。

  1. len(buf) < min
    ,则返回 io.ErrShortBuffer
    ,因为缓冲区装不下最小读取量啊!
  2. 当读取的内容长度 n
    不足 min
    时,如果 n == 0
    ,说明 Reader
    对象内容为空,则返回 io.EOF
    错误;如果 0 < n < min
    ,说明只读取到了部分数据,则返回 io.ErrUnExpectedEOF
  3. 当且仅当 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)
}