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

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)
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章