Go - io 標準函式庫學習筆記
關於 Go 語言 io 標準函式庫的一些閱讀後的筆記
Reader 的定義
Reader 是一個包裝了基本 Read 方法的 interface。
Read 讀取
len(p)
位元組到p
,它會回傳位元組數n
(0 <= n <= len(p)
)和任何遇到的錯誤。即使 Read 回傳的n
小於len(p)
,它也會在調用過程中,使用 p 作為暫存空間。如果一些資料可以使用,但是不滿足len(p)
位元組長度,一般 Read 會回傳可使用的資料,而不是繼續等待更多的資料。當 Read 在成功讀取
n > 0
位元組遇到錯誤,或者是 EOF(end-of-file) 狀態時,它會回傳讀取到的位元組數。它會從相同的調用回傳非 nil 的錯誤或者是從一個隨後調用中回傳錯誤(n 同時為 0)。一般常見的情況是,一個 Reader 在 input stream 結束時,回傳一個非 0 的位元組長度,可能是err == EOF
或是err == nil
。下一個 Read 應該回傳0, EOF
。Callers 總是要考慮到在錯誤 err 之前,應該處理回傳的
n > 0
位元組長度。這樣做可以正確處理在讀取一些位元組後所發生的 I/O 錯誤,同時也允許 EOF 行為。Read 的實作不希望回傳一個
0
位元組的長度和一個nil
的 error,除非當len(p) == 0
。Callers 應該對於一個回傳0
和nil
作為什麼事都沒發生,特別是它並不表示是 EOF。
Reader 的 interface 定義:
type Reader interface {Read(p []byte) (n int, err error)}
範例:
// Source: https://github.com/polaris1119/The-Golang-Standard-Library-by-Example/blob/master/chapter01/01.1.mdfunc ReadFrom(reader io.Reader, num int) ([]byte, error) {p := make([]byte, num)n, err := reader.Readerif n > 0 {retrun p[:n], nil}return p, err}func main() {data, err := ReadFrom(strings.NewReader("Write Something...", 20))if err != nil {log.Fatal(err)}fmt.Println(string(data))}
ReadFrom
函式接受 reader
和 num
參數,其中 reader
的型別是 io.Reader
,所以只要有實作 io.Reader
這個型別都可以作為參數傳入,函式最後回傳了 ([]byte, error)
。
在 ReadFrom
函式的第五行,我們可以取得實作 io.Reader
的 reader
所回傳的位元組長度以及錯誤;在第十四行,我們使用了 strings
標準函式庫的 NewReader
方法,根據文件說明 strings.NewReader
回傳一個新的 Reader
,詳細請參考原始碼,所以這符合傳入 ReadFrom
的第一個參數所要求的型別。
Writer 的定義
Writer 是一個包裝基本 Write 方法的 interface。
Write 從 p 寫入
len(p)
位元組長度到底層的 data stream。它從 p 回傳(0 <= n <= len(p))
的位元組數以及任何遇到造成寫入提早停止的任何錯誤。如果回傳
n < len(p)
,Write 必須回傳一個非nil
的錯誤。即使暫停,Write 也不能修改 slice 資料。
Writeer 的 interface 定義:
type Writer interface {Write(p []byte) (n int, err error)}
範例:
func main() {var b bytes.Bufferb.Write([]byte("Hello! "))fmt.Fprint(&b, "My name is P.J.")b.WriteTo(os.Stdout)}
這裡我們使用 bytes.Buffer 作為範例,因為 Buffer
實作了 io 的方法,在第三行我們對 Write
方法傳入了一個 slice,fmt.Fprint
第一個參數接收一個 io.Writer
型別的參數,它會將第二個參數的內容寫入到第一個參數的 slice 內,實際上 fmt.Fprint
的第二個參數是一個可變參數,也就是說我也可以傳入像是 fmt.Fprint(&b, 1, "Hi", 2, "Ho")
帶有多個參數,詳細可以參考 fmt.Fprint
的定義,最後我們使用了 b.WriteTo(os.Stdout)
輸出訊息到終端機。
os.Stdout
是來自 os
標準函式庫內的變數:
var (Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr"))
實際上,Stdin
、Stdout
、Stderr
是對應到 Linux 下的輸入、輸出,和錯誤描述,根據 NewFile 的定義,它們都回傳了 *File 這個 type,而 *File
也都實作了 io 的 Reader 和 Writer。
Go 的
io
標準函式庫提供了最基本的 interface,它主要的工作是封裝一些現有原始(primitive)的實作,把抽象的功能轉換爲公開的 interface,所以我們並不需要關心具體的邏輯。
心得
我們在撰寫程式碼時,需要注意方法所接收的參數型別,例如某個函式的定義是 func someFn(r io.Reader)
,那麼我們傳入的 r
就必須一定就必須一定要實作 io.Reader
,我們不需要關心實際的實作細節,例如在 Reader 的範例;如果同時實作了 io.Reader
和 io.Writer
,就可以進行資料的讀取和寫入囉。