# Day 27 : Golang interface(5) - io.Reader & io.Writer 由於想更瞭解我們實際使用不同package的過程中是如何使用到interface,這裡想專門跟著Udemy課程做個學習紀錄。 目標是要透過http.Get取得response的body資料,根據官方文件的Get函式開始進行Document的閱讀,其步驟如下,會發現我們要看到第4步,才能瞭解要透過Read方法取得body資料。 1. Get函式定義 `func Get(url string) (resp *Response, err error)` 2. resp為一個指向Response物件的指針,Response結構體定義如下 ``` type Response struct { Status string // e.g. "200 OK" StatusCode int // e.g. 200 Proto string // e.g. "HTTP/1.0" ProtoMajor int // e.g. 1 ProtoMinor int // e.g. 0 Header Header Body io.ReadCloser ...略} ``` 3. ReadCloser唯一個interface屬於io package,定義如下 ``` type ReadCloser interface { Reader Closer } ``` 4. Reader介面定義如下 ``` type Reader interface { Read(p []byte) (n int, err error) } ``` 5. Closer介面定義如下 ``` type Closer interface { Close() error } ``` ![](https://i.imgur.com/NAqmdFM.png) (圖一,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/)) 這裡可以看到ReadCloser介面,定義Reader介面和Closer介面,也就是說任何實現ReadCloser 介面的型別,也必須同時實現Reader介面和Closer介面。總結來說這邊可以看到Body其實可以是任意型別的值,只要有方法是符合Reader 介面定義的Read()和Closer interface定義的Close()。 ## Reader interface and Read function 在Go裡面Reader介面其實可以用在很多地方,不單單只是就是用來取得Body資料,以圖二來說,如果有許多不同來源的input,會需要建立多個接受不同型別作為參數的function,然後產生不同型別的output。但如果使用Reader介面,會發現不管你的來源是什麼資料,只要你的型別實現了Reader介面,就可以讀取內容了 ![](https://i.imgur.com/QdqnFrF.png) (圖二,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/)) ![](https://i.imgur.com/ghrgM87.png) (圖三,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/)) 那什麼又是Read function呢,其原理比較複雜,課程中透過圖四來介紹。 在圖四最左邊,代表誰想要實現Reader interface,就要先建立屬於自己的byte slice,這次的例子就可以看成我們想要透過Read function 讀取body的資料。 創造一個byte slice後,會將其傳入給實現Reader interface的型別,要如何實現Reader interface呢,就是要建立Read function。所以就是要建立一個byte slice傳入Read function,當Read函式取得response body之後,就會將資料寫入到byte slice。 接下來看回傳值,err比較單純就是用來做錯誤處裡,而n我們可以先看看[官方文件](https://pkg.go.dev/io#Reader)的定義 > *Read reads up to `len(p)` bytes into p. It returns the number of bytes read (0 <= n <= `len(p)`) and any error encountered. * 也就是說n代表Read函式讀取到的byte值,n如果大於0代表讀取到有效的內容,會繼續執行。一般來說Read函式會其實是一直被使用,每次會讀取一定量的byte,存到我們建立的byte slice,直到Read函數返回的n為0,代表已讀取完全部的資料。 至於為什麼要回傳一個整數n,不直接回傳byte slice呢?這邊我其實還不是太瞭解,但根據這篇[Reddit](https://www.reddit.com/r/golang/comments/uuu52z/readerreadbyte_design/)的回答,就是為了讓使用者可以透過緩衝區重複使用來有效利用memory,如果返回的是一個slice,每次使用都要重新分配memory,可能就會造成浪費。 舉例來說,讀取一個1GB的文件時,可以使用1MB的緩衝區進行讀取和處理,這樣可以重複使用緩衝區,而不需要每次都重新分配1GB的memory。 ![](https://i.imgur.com/apX9NWM.png) (圖四,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/)) ## 程式碼實作 說這麼多,還是回到程式碼實作,看是如何一步步實踐上面的說明。 一開始透過http.Get取得一個指向Resonpse結構體的指針resp。這個結構體有定義一個Body,型別為io.ReadCloser介面。io.ReadCloser介面同時又滿足Read介面和Closer介面,所以可以使用Read函式和Close函式。 所以我們先定義一個很大的byte slice,然後傳入Read函式。當執行完Read函式時,byte slice就已經被更新完成,透過fmt.Println就可以得到我們要的資訊 ```go= func main() { resp, err := http.Get("http://google.com/") if err != nil { fmt.Println("error:",err) os.Exit(1) } bs := make([]byte, 99999) resp.Body.Read(bs) fmt.Println(string(bs)) // 得到我們要的google網站的body資訊,也就是構成這個網站的html檔案 } ``` ## io.Writer & io.Copy & os.Stdout 那每次我們需要透過Read函式讀取資料時,都要先建立一個byte slice嗎,會不會太麻煩。 要解決這個問題要先介紹Go裡面另外一個很強大的介面 - Writer interface。 ``` type Writer interface { Write(p []byte) (n int, err error) } ``` 我們可以看到Writer和Reader interface定義非常像,而需要注意的是Write function 和 Read function雖然接受都是byte slice,但這裡的byte slice其實已經有資料,而非Read function要傳入的是一個空的byte slice。因為這時候不是要讀取資料了,Write function是要將已經拿到的資料,轉出去給任何形式的output(圖五),例如寫到termianl、寫到某個text file等等。 ![](https://i.imgur.com/xR1MAhA.png) (圖五,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/)) 接下來是io.Copy ``` func Copy(dst Writer, src Reader) (written int64, err error) { return copyBuffer(dst, src, nil) } ``` 透過這個函式,可以我們可以透過copyBuffer這個函式,實現Readr介面的資料複製到目標的Writer介面,什麼東西實現Writer介面呢? 這裡要引入Go的標準輸出概念Stdout。 os.Stdout其型別為一個指向os.File的指針,而File在os package定義的一個型別,裡面有Write函式,所以os.Stuout可以實現Writer介面(圖六) `func (f *File) Write(b []byte) (n int, err error)` ![](https://i.imgur.com/1pu0QfP.png) (圖六,圖片來源 : [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/)) 總和以上推論可以將程式碼修改如下,更為精簡 ```go! func main() { resp, err := http.Get("http://google.com/") if err != nil { fmt.Println("error:",err) os.Exit(1) } io.Copy(os.Stdout, resp.Body) } ``` ## References 1. [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/) 2. [Golang net/http package](https://pkg.go.dev/net/http@go1.20.2#Get) 3. [Reader.Read([]byte) design](https://www.reddit.com/r/golang/comments/uuu52z/readerreadbyte_design/) 4. [良葛格 - io.Reader、io.Writer](https://openhome.cc/Gossip/Go/ReaderWriter.html) 5. [Golang - 認識 io package 的原理與操作](https://blog.kennycoder.io/2020/02/08/Golang-%E8%AA%8D%E8%AD%98-io-package%E7%9A%84%E5%8E%9F%E7%90%86%E8%88%87%E6%93%8D%E4%BD%9C/) ###### tags: `About Go`