io.Reader 解析

簡(jiǎn)介

io.Reader 是一個(gè) Interface 類型,功能非常強(qiáng)大,在任何需要讀的地方我們都盡量使用它。先來看下它的原型:

type Reader interface {
  Read(p []byte) (n int, err error)
}

可見,任何實(shí)現(xiàn)了 Read() 函數(shù)的對(duì)象都可以作為 Reader 來使用姜凄。

Reader 類型

標(biāo)準(zhǔn)庫中有許多不同的 Reader 類型政溃,最常見的就是 bytes, strings 等幾個(gè)庫。我們或多或少都用過它們态秧。下面來看幾個(gè)最常見的例子:

(1) 文件操作
當(dāng)我們調(diào)用 os.Open()打開一個(gè)文件董虱,它會(huì)返回一個(gè) os.File 對(duì)象,而這個(gè)對(duì)象其實(shí)就是一個(gè) Reader 類型(因?yàn)樗鼘?shí)現(xiàn)了 Read 函數(shù)):

var r io.Reader
var err error
r, err = os.Open("file.txt")

(2)字符串
我們還可以從一個(gè)普通的字符串 string 來創(chuàng)建一個(gè) Reader申鱼,如下:

var r io.Reader
r = strings.NewReader("Read will return these bytes")

(3)網(wǎng)絡(luò) net

<1> net.Conn 是一個(gè) Reader/Writer
原型:

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    LocalAddr() Addr
    RemoteAddr() Addr
    SetDeadline(t time.Time) error
    SetReadDeadline(t time.Time) error
    SetWriteDeadline(t time.Time) error
}

當(dāng)然愤诱,提供的一個(gè)私有類型 conn 是一個(gè) struct,其實(shí)現(xiàn)了上述 interface捐友。

type conn struct {
    fd *netFD
}
func (c *conn) Read(b []byte) (int, error) {
    ...
}
  
// Write implements the Conn Write method.
func (c *conn) Write(b []byte) (int, error) {
    ...
}

使用場(chǎng)景:

conn, err := net.Dial("tcp", "google.com:80")
tmp := make([]byte, 256)     // using small tmo buffer for demonstrating
for {
        n, err := conn.Read(tmp)
        if err != nil {
            if err != io.EOF {
                fmt.Println("read error:", err)
            }
            break
        }
        buf = append(buf, tmp[:n]...)
}

或者

func main() {
    conn, err := net.Dial("tcp", "google.com:80")
    if err != nil {
        fmt.Println("dial error:", err)
        return
    }
    defer conn.Close()
    fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
    var buf bytes.Buffer
    io.Copy(&buf, conn)
    fmt.Println("total size:", buf.Len())
}

或者

func whois(domain, server string) ([]byte, error) {
    conn, err := net.Dial("tcp", server+":43")
    if err != nil {
        return nil, err
    }
    defer conn.Close()
    fmt.Fprintf(conn, "%s\r\n", domain)
    return ioutil.ReadAll(conn)
}

<2> http.Request 中的 body 數(shù)據(jù)就是一個(gè) Reader

type Request struct {
    Method string
    URL *url.URL
    Header Header
    // Body is the request's body.
    Body io.ReadCloser
    Host string
    Form url.Values
    PostForm url.Values
    MultipartForm *multipart.Form
    ...
}

可以這樣使用:

var r io.Reader
r = request.Body

<3> http.Post() 請(qǐng)求
原型:

func (c *Client) Post(url string, contentType string, body io.Reader) (resp *Response, err error)

用法

requestBytes, _ := json.Marshal(request)
requestBuf := bytes.NewBuffer(requestBytes)
httpResponse, _ := c.client.Post(c.address, "text/json", requestBuf)
responseBytes, _ := ioutil.ReadAll(httpResponse.Body)
response := &types.RPCResponse{}
err = json.Unmarshal(responseBytes, response)

或者

resp, err := http.Post("www.example.com/accept.php", "application/x-www-form-urlencoded", strings.NewReader("name=alex"))

(4) bytes.Buffer
是 struct 類型淫半,但同時(shí)也是一個(gè) Reader/Writer,因?yàn)樗鼘?shí)現(xiàn)了 Read() /Writer() 等函數(shù)匣砖。

type Buffer struct {
    buf       []byte   // contents are the bytes buf[off : len(buf)]
    off       int      // read at &buf[off], write at &buf[len(buf)]
    bootstrap [64]byte // memory to hold first slice; helps small buffers avoid allocation.
    lastRead  readOp   // last read operation, so that Unread* can work correctly.
}

func (b *Buffer) Read(p []byte) (n int, err error) {
    b.lastRead = opInvalid
    if b.off >= len(b.buf) {
        // Buffer is empty, reset to recover space.
        b.Truncate(0)
        if len(p) == 0 {
            return
        }
        return 0, io.EOF
    }
    n = copy(p, b.buf[b.off:])
    b.off += n
    if n > 0 {
        b.lastRead = opRead
    }
    return
}

為 []byte 創(chuàng)建一個(gè) Reader
有時(shí)候科吭,我們有一個(gè)字節(jié)序列,但是需要調(diào)用的函數(shù)只接收 Reader猴鲫,那么我們可以通過 bytes.NewReader() 來創(chuàng)建一個(gè) Reader 來傳入其中对人。


bs, err := json.Marshal(data)
if err != nil {
    return err
}

return io.Copy(os.Stdout, bytes.NewReader(bs))

(5) bufio
標(biāo)準(zhǔn)庫 bufio 提供了對(duì) io.Reader 和 io.Writer 等 I/O 對(duì)象的封裝,實(shí)現(xiàn)了緩沖的功能拂共。
比如 :

func NewReader(rd io.Reader) *Reader 
func NewWriterSize(w io.Writer, size int) *Writer
func NewReadWriter(r *Reader, w *Writer) *ReadWrite
func NewScanner(r io.Reader) *Scanner

例子:
從標(biāo)準(zhǔn)輸入讀取內(nèi)容到 buffer 中

reader := bufio.NewReader(os.Stdin)

或者初始化一個(gè) bytes.Buffer 對(duì)象牺弄,然后據(jù)此生成一個(gè) bufio.Reader 對(duì)象

readbuffer := bytes.NewBuffer([]byte("input string to be read into new buffer"))
reader := bufio.NewReader(readbuffer)

由于 net.Conn 也是一個(gè) Reader,因此還可以為它來創(chuàng)建一個(gè) bufio.Reader宜狐,如下:

listener, err:= net.Listen("tcp", ":8888")
for {
    conn, _ := listener.Accept()
    ...
}
reader := bufio.NewReader(conn)

(6) 編解碼 Decoder

func receive(conn net.Conn) {
  dec := json.NewDecoder(conn)
  msg := new(Message)
  err := dec.Decode(msg)
}

原型

func NewDecoder(r io.Reader) *Decoder {
    return &Decoder{r: r}
}

注: 由于 conn 是一個(gè) interface势告,聲明了 Read() , Write() 等方法, 因此這里很方便的就可以作為 Reader 使用了。

除此之外抚恒,GO 語言標(biāo)準(zhǔn)庫中還有很多 Reader 類型培慌,我們應(yīng)該在一切能使用它們的場(chǎng)景下都盡量使用它們。

使用 Reader

上面我們看了一些不同的 Reader 類型柑爸,現(xiàn)在我們看一下如何使用它們

(1)直接調(diào)用該 Reader 對(duì)象的 Read() 函數(shù)來讀取數(shù)據(jù)

p := make([]byte, 256)
n, err := r.Read(p)

(2)使用 ioutil.ReadAll 從一個(gè) Reader 中讀取數(shù)據(jù)吵护,返回 raw []byte

b, err := ioutil.ReadAll(r)

(3)使用 io.Copy() 從一個(gè) Reader 中讀取數(shù)據(jù),并寫入一個(gè) Writer

n, err := io.Copy(w, r)

(4)使用 JSON Decoder 從一個(gè) Reader 中直接 decode 數(shù)據(jù)

err := json.NewDecoder(r).Decode(v)

(5)從一個(gè)已經(jīng) gzipped 了的 []byte 數(shù)據(jù)的 Reader 中創(chuàng)建一個(gè) gzip.Reader

r = gzip.NewReader(r)

這樣的話,從這個(gè)新創(chuàng)建的 reader 中讀取數(shù)據(jù)的話就是已經(jīng)自動(dòng) unzipped 的數(shù)據(jù)了馅而。

設(shè)計(jì) Reader 接口

在編寫應(yīng)用程序接口的時(shí)候祥诽,如果我們需要接受 []byte 或者 string 等類型的數(shù)據(jù),我們可以把參數(shù)設(shè)置成接受 Reader 類型而不是 []byte 或 string瓮恭,這樣的話雄坪,我們的接口就能變得更加通用,適用的場(chǎng)景也會(huì)更多屯蹦。
如下:

func Reverse(s string) (string, error)

可以變成

func Reverse(r io.Reader) io.Reader

這樣改變之后维哈,如果別人想要傳入一個(gè) string,那么它可以這樣:

r = Reverse(strings.NewReader("Make me backwards"))

如果別人想要傳入一個(gè)文件對(duì)象登澜,可以這樣:

f, err := os.Open("file.txt")
if err != nil {
  log.Fatalln(err)
}
r = Reverse(f)

或者阔挠,還可以傳入一個(gè) http.Request.Body

func handle(w http.ResponseWriter, r *http.Request) {
  rev := Reverse(r.Body)
  // etc...
}

最后建議:
在任何需要處理數(shù)據(jù)流的地方,都應(yīng)該盡可能使用 Reader/Writer脑蠕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末购撼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谴仙,更是在濱河造成了極大的恐慌迂求,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晃跺,死亡現(xiàn)場(chǎng)離奇詭異揩局,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)掀虎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門谐腰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人涩盾,你說我怎么就攤上這事十气。” “怎么了春霍?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵砸西,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我址儒,道長(zhǎng)芹枷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任莲趣,我火速辦了婚禮鸳慈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喧伞。我一直安慰自己走芋,他們只是感情好绩郎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著翁逞,像睡著了一般肋杖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挖函,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天状植,我揣著相機(jī)與錄音,去河邊找鬼怨喘。 笑死津畸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的必怜。 我是一名探鬼主播肉拓,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼棚赔!你這毒婦竟也來了帝簇?” 一聲冷哼從身側(cè)響起徘郭,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤靠益,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后残揉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胧后,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年抱环,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了壳快。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡镇草,死狀恐怖眶痰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梯啤,我是刑警寧澤竖伯,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站因宇,受9級(jí)特大地震影響七婴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜察滑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一打厘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贺辰,春花似錦户盯、人聲如沸嵌施。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽艰管。三九已至,卻和暖如春蒋川,著一層夾襖步出監(jiān)牢的瞬間牲芋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工捺球, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缸浦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓氮兵,卻偏偏與公主長(zhǎng)得像裂逐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泣栈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容