Go語言實戰筆記(十九)| Go Writer 和 Reader

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org或者網站http://www.flysnow.org/,第一時間看後續筆記。覺得有協助的話,順手分享到朋友圈吧,感謝支援。

輸入和輸出

Go Writer 和 Reader介面的設計遵循了Unix的輸入和輸出,一個程式的輸出可以是另外一個程式的輸入。他們的功能單一併且純粹,這樣就可以非常容易的編寫程式碼,又可以通過組合的概念,讓我們的程式做更多的事情。

比如我們在上一篇的Go log日誌 http://www.flysnow.org/2017/05/06/go-in-action-go-log.html 中,就介紹了Unix的三種輸入輸出輸入模式,他們對應的Go語言裡有專門的實現。

12345
var (Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr"))

這三種標準的輸入和輸出都是一個*File,而*File恰恰就是同時實現了io.Writerio.Reader這兩個介面的類型,所以他們同時具備輸入和輸出的功能,既可以從裡面讀取資料,又可以往裡面寫入資料。

Go標準庫的io包也是基於Unix這種輸入和輸出的理念,大部分的介面都是擴充了io.Writerio.Reader,大部分的類型也都選擇的實現了io.Writerio.Reader這兩個介面,然後把資料的輸入和輸出,抽象為流的讀寫,所以只要實現了這兩個介面,都可以使用流的讀寫功能。

io.Writerio.Reader兩個介面的高度抽象,讓我們不用再面向具體的業務,我們只關注,是讀還是寫,只要我們定義的方法函數可以接收這兩個介面作為參數,那麼我們就可以進行流的讀寫,而不用關心如何讀,寫到哪裡去,這也是面向介面編程的好處。

Reader和Writer介面

這兩個高度抽象的介面,只有一個方法,也體現了Go介面設計的簡潔性,只做一件事。

123456789101112
// Writer is the interface that wraps the basic Write method.//// Write writes len(p) bytes from p to the underlying data stream.// It returns the number of bytes written from p (0 <= n <= len(p))// and any error encountered that caused the write to stop early.// Write must return a non-nil error if it returns n < len(p).// Write must not modify the slice data, even temporarily.//// Implementations must not retain p.type Writer interface {Write(p []byte) (n int, err error)}

這是Wirter介面的定義,它只有一個Write方法,接受一個byte的切片,返回兩個值,n表示寫入的位元組數、err表示寫入時發生的錯誤。

從其文檔注釋來看,這個方法是有規範要求的,我們要想實現一個io.Writer介面,就要遵循這些規則。

  1. write方法向底層資料流寫入len(p)位元組的資料,這些資料來自於切片p
  2. 返回被寫入的位元組數n,0 <= n <= len(p)
  3. 如果n<len(p), 則必須返回一些非nil的err
  4. 如果中途出現問題,也要返回非nil的err
  5. Write方法絕對不能修改切片p以及裡面的資料

這些實現io.Writer介面的規則,所有實現了該介面的類型都要遵守,不然可能會導致莫名其妙的問題。

12345678910111213141516171819202122232425262728293031
// Reader is the interface that wraps the basic Read method.//// Read reads up to len(p) bytes into p. It returns the number of bytes// read (0 <= n <= len(p)) and any error encountered. Even if Read// returns n < len(p), it may use all of p as scratch space during the call.// If some data is available but not len(p) bytes, Read conventionally// returns what is available instead of waiting for more.//// When Read encounters an error or end-of-file condition after// successfully reading n > 0 bytes, it returns the number of// bytes read. It may return the (non-nil) error from the same call// or return the error (and n == 0) from a subsequent call.// An instance of this general case is that a Reader returning// a non-zero number of bytes at the end of the input stream may// return either err == EOF or err == nil. The next Read should// return 0, EOF.//// Callers should always process the n > 0 bytes returned before// considering the error err. Doing so correctly handles I/O errors// that happen after reading some bytes and also both of the// allowed EOF behaviors.//// Implementations of Read are discouraged from returning a// zero byte count with a nil error, except when len(p) == 0.// Callers should treat a return of 0 and nil as indicating that// nothing happened; in particular it does not indicate EOF.//// Implementations must not retain p.type Reader interface {Read(p []byte) (n int, err error)}

這是io.Reader介面定義,也只有一個Read方法,這個方法接受一個byte的切片,並返回兩個值,一個是讀入的位元組數,一個是err錯誤。

從其注釋文檔看,io.Reader介面的規則更多。

  1. Read最多讀取len(p)位元組的資料,並儲存到p。
  2. 返回讀取的位元組數以及任何發生的錯誤資訊
  3. n要滿足0 <= n <= len(p)
  4. n<len(p)時,表示讀取的資料不足以填滿p,這時方法會立即返回,而不是等待更多的資料
  5. 讀取過程中遇到錯誤,會返回讀取的位元組數n以及相應的錯誤err
  6. 在底層輸入資料流結束時,方法會返回n>0的位元組,但是err可能時EOF,也可以是nil
  7. 在第6種(上面)情況下,再次調用read方法的時候,肯定會返回0,EOF
  8. 調用Read方法時,如果n>0時,優先處理處理讀入的資料,然後再處理錯誤err,EOF也要這樣處理
  9. Read方法不鼓勵返回n=0並且err=nil的情況,

規則稍微比Write介面有點多,不過也都比較好理解,注意第8條,即使我們在讀取的時候遇到錯誤,但是也應該也處理已經讀到的資料,因為這些已經讀到的資料是正確的,如果不進行處理丟失的話,讀到的資料就不完整了。

《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org或者網站http://www.flysnow.org/,第一時間看後續筆記。覺得有協助的話,順手分享到朋友圈吧,感謝支援。

樣本

對這兩個介面瞭解後,我們就可以嘗試使用他們了,現在來看個例子。

1234567891011
func main() {//定義零值Buffer類型變數bvar b bytes.Buffer//使用Write方法為寫入字串b.Write([]byte("你好"))//這個是把一個字串拼接到Buffer裡fmt.Fprint(&b,",","http://www.flysnow.org")//把Buffer裡的內容列印到終端控制台b.WriteTo(os.Stdout)}

這個例子是拼接字串到Buffer裡,然後再輸出到控制台,這個例子非常簡單,但是利用了流的讀寫,bytes.Buffer是一個可變位元組的類型,可以讓我們很容易的對位元組進行操作,比如讀寫,追加等。bytes.Buffer實現了io.Writerio.Reader介面,所以我麼可以很容易的進行讀寫操作,而不用關注具體實現。

b.Write([]byte("你好"))實現了寫入一個字串,我們把這個字串轉為一個位元組切片,然後調用Write方法寫入,這個就是bytes.Buffer為了實現io.Writer介面而實現的一個方法,可以幫我們寫入資料流。

12345
func (b *Buffer) Write(p []byte) (n int, err error) {b.lastRead = opInvalidm := b.grow(len(p))return copy(b.buf[m:], p), nil}

以上就是bytes.Buffer實現io.Writer介面的方法,最終我們看到,我們寫入的切片會被拷貝到b.buf裡,這裡b.buf[m:]拷貝其實就是追加的意思,不會覆蓋已經存在的資料。

從實現看,我們發現其實只有b *Buffer指標實現了io.Writer介面,所以我們範例程式碼中調用fmt.Fprint函數的時候,傳遞的是一個地址&b

1234567
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {p := newPrinter()p.doPrint(a)n, err = w.Write(p.buf)p.free()return}

這是函數fmt.Fprint的實現,它的功能就是為一個把資料a寫入到一個io.Writer介面實現了,具體如何寫入,它是不關心的,因為io.Writer會做的,它只關心可以寫入即可。w.Write(p.buf)調用Wirte方法寫入。

最後的b.WriteTo(os.Stdout)是把最終的資料輸出到標準的os.Stdout裡,以便我們查看輸出,它接收一個io.Writer介面類型的參數,開篇我們講過os.Stdout也實現了這個io.Writer介面,所以就可以作為參數傳入。

這裡我們會發現,很多方法的接收參數都是io.Writer介面,當然還有io.Reader介面,這就是面向介面的編程,我們不用關注具體實現,只用關注這個介面可以做什麼事情,如果我們換成輸出到檔案裡,那麼也很容易,只用把os.File類型作為參數即可。任何實現了該介面的類型,都可以作為參數。

除了b.WriteTo方法外,我們還可以使用io.Reader介面的Read方法實現資料的讀取.

123
var p [100]byten,err:=b.Read(p[:])fmt.Println(n,err,string(p[:n]))

這是最原始的方法,使用Read方法,n為讀取的位元組數,然後我們輸出列印出來。

因為byte.Buffer指標實現了io.Reader介面,所以我們還可以使用如下方式讀取資料資訊。

12
   data,err:=ioutil.ReadAll(&b)fmt.Println(string(data),err)

ioutil.ReadAll介面一個io.Reader介面的參數,表明可以從任何實現了io.Reader介面的類型裡讀取全部的資料。

123456789101112131415161718
func readAll(r io.Reader, capacity int64) (b []byte, err error) {buf := bytes.NewBuffer(make([]byte, 0, capacity))// If the buffer overflows, we will get bytes.ErrTooLarge.// Return that as an error. Any other panic remains.defer func() {e := recover()if e == nil {return}if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {err = panicErr} else {panic(e)}}()_, err = buf.ReadFrom(r)return buf.Bytes(), err}

以上是ioutil.ReadAll實現的原始碼,也非常簡單,基本原理是建立一個byte.Buffer ,通過這個byte.BufferReadFrom方法,把io.Reader裡的資料讀取出來,最後通過byte.BufferBytes方法進行返回最終讀取的位元組資料資訊。

整個流的讀取和寫入已經被完全抽象啦, io包的大部分操作和類型都是基於這兩個介面,當然還有http等其他牽涉到資料流、檔案流等的,都可以完全用io.Writerio.Reader介面來表示,通過這兩個介面的串連,我們可以實現任何資料的讀寫。

《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org或者網站http://www.flysnow.org/,第一時間看後續筆記。覺得有協助的話,順手分享到朋友圈吧,感謝支援。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.