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)
}