Go編程技巧--io.Reader/Writer

Go原生的pkg中有一些核心的interface,其中io.Reader/Writer是比較經常使用的接口。不少原生的結構都圍繞這個系列的接口展開,在實際的開發過程當中,你會發現經過這個接口能夠在多種不一樣的io類型之間進行過渡和轉化。本文結合實際場景來總結一番。網絡

總覽

image.png

圍繞io.Reader/Writer,有幾個經常使用的實現:ui

  • net.Conn, os.Stdin, os.File: 網絡、標準輸入輸出、文件的流讀取
  • strings.Reader: 把字符串抽象成Reader
  • bytes.Reader: 把[]byte抽象成Reader
  • bytes.Buffer: 把[]byte抽象成Reader和Writer
  • bufio.Reader/Writer: 抽象成帶緩衝的流讀取(好比按行讀寫)

這些實現對於初學者來講其實比較難去記憶,在遇到實際問題的時候更是一臉蒙圈,不知如何是好。下面用實際的場景來舉例編碼

場景舉例

0. base64編碼成字符串

encoding/base64包中:spa

func NewEncoder(enc *Encoding, w io.Writer) io.WriteCloser

這個用來作base64編碼,可是仔細觀察發現,它須要一個io.Writer做爲輸出目標,並用返回的WriteCloser的Write方法將結果寫入目標,下面是Go官方文檔的例子code

input := []byte("foo\x00bar")
encoder := base64.NewEncoder(base64.StdEncoding, os.Stdout)
encoder.Write(input)

這個例子是將結果寫入到Stdout,若是咱們但願獲得一個字符串呢?觀察上面的圖,否則發現能夠用bytes.Buffer做爲目標io.Writer接口

input := []byte("foo\x00bar")
buffer := new(bytes.Buffer)
encoder := base64.NewEncoder(base64.StdEncoding, buffer)
encoder.Write(input)
fmt.Println(string(buffer.Bytes())

1. []byte和struct之間正反序列化

這種場景常常用在基於字節的協議上,好比有一個具備固定長度的結構:開發

type Protocol struct {
    Version        uint8
    BodyLen        uint16
    Reserved    [2]byte
    Unit         uint8
    Value         uint32
}

經過一個[]byte來反序列化獲得這個Protocol,一種思路是遍歷這個[]byte,而後逐一賦值。其實在encoding/binary包中有個方便的方法:rem

func Read(r io.Reader, order ByteOrder, data interface{}) error

這個方法從一個io.Reader中讀取字節,並已order指定的端模式,來給填充data(data須要是fixed-sized的結構或者類型)。要用到這個方法首先要有一個io.Reader,從上面的圖中不難發現,咱們能夠這麼寫:文檔

var p Protocol
var bin []byte
//...
binary.Read(bytes.NewReader(bin), binary.LittleEndian, &p)

換句話說,咱們將一個[]byte轉成了一個io.Reader字符串

反過來,咱們須要將Protocol序列化獲得[]byte,使用encoding/binary包中有個對應的Write方法:

func Write(w io.Writer, order ByteOrder, data interface{}) error

經過將[]byte轉成一個io.Writer便可:

var p Protocol
buffer := new(bytes.Buffer)
//...
binary.Writer(buffer, binary.LittleEndian, p)
bin := buffer.Bytes()

2. 從流中按行讀取

好比對於常見的基於文本行的HTTP協議的讀取,咱們須要將一個流按照行來讀取。本質上,咱們須要一個基於緩衝的讀寫機制(讀一些到緩衝,而後遍歷緩衝中咱們關心的字節或字符)。在Go中有一個bufio的包能夠實現帶緩衝的讀寫:

func NewReader(rd io.Reader) *Reader
func (b *Reader) ReadString(delim byte) (string, error)

這個ReadString方法從io.Reader中讀取字符串,直到delim,就返回delim和以前的字符串。若是將delim設置爲\n,至關於按行來讀取了:

var conn net.Conn
//...
reader := NewReader(conn)
for {
    line, err := reader.ReadString([]byte('\n'))
    //...
}

花式技(zuo)巧(si)

string轉[]byte

a := "Hello, playground"
fmt.Println([]byte(a))

等價於

a := "Hello, playground"
buf := new(bytes.Buffer)
buf.ReadFrom(strings.NewReader(a))
fmt.Println(buf.Bytes())
相關文章
相關標籤/搜索