32

go io 之 Read / ReadAtLeast / ReadFull / EOF / ErrUnexpectedEOF

 5 years ago
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.
neoserver,ios ssh client

goio 包提供了 ReadFull / ReadAtLeast 函数对 Reader 对象进行读操作,任何实现 io.Reader 接口的对象都可以使用这两个方法,同时还延伸出 io.EOF / io.ErrUnexpectedEOF 错误,下面实践一下。

io.Reader Interface & io.Reader.Read

Reader 对象必须实现了 io.Reader 接口,此接口约定了 Read 方法,实现此方法的对象都可以使用 go io 提供的其他方法进行读操作,比如 ReadAtLeast/ReadFullio.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 中,并返实际读取到的字节数 nerrerrnilio.EOF ,具体的返回规则如下:

  1. 如果 n == len(p)0 < n < len(p) ,则 errnil (即至少读到了一些东西)。
  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 <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

  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 源码解读

ReaderAtLeastReader 对象的内容读取到 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 {
        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)
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK