io.Reader 解析

简介

io.Reader 是一个 Interface 类型,功能非常强大,在任何需要读的地方我们都尽量使用它。先来看下它的原型:

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

可见,任何实现了 Read() 函数的对象都可以作为 Reader 来使用。

Reader 类型

标准库中有许多不同的 Reader 类型,最常见的就是 bytes, strings 等几个库。我们或多或少都用过它们。下面来看几个最常见的例子:

文件操作

当我们调用 os.Open()打开一个文件,它会返回一个 os.File 对象,而这个对象其实就是一个 Reader 类型(因为它实现了 Read 函数):

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

字符串

我们还可以从一个普通的字符串 string 来创建一个 Reader,如下:

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

网络 net

net.Conn 是一个 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
}

当然,提供的一个私有类型 conn 是一个 struct,其实现了上述 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) {
    ...
}

使用场景:

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)
}

http.Request 中的 body 数据就是一个 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

http.Post() 请求

原型:

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"))

bytes.Buffer

是 struct 类型,但同时也是一个 Reader/Writer,因为它实现了 Read() /Writer() 等函数。

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
}

bufio

标准库 bufio 提供了对 io.Reader 和 io.Writer 等 I/O 对象的封装,实现了缓冲的功能。

比如 :

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

例子:

从标准输入读取内容到 buffer 中

reader := bufio.NewReader(os.Stdin)

或者初始化一个 bytes.Buffer 对象,然后据此生成一个 bufio.Reader 对象

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

由于 net.Conn 也是一个 Reader,因此还可以为它来创建一个 bufio.Reader,如下:

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

编解码 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 是一个 interface,声明了 Read() , Write() 等方法, 因此这里很方便的就可以作为 Reader 使用了。

除此之外,GO 语言标准库中还有很多 Reader 类型,我们应该在一切能使用它们的场景下都尽量使用它们。

使用 Reader

上面我们看了一些不同的 Reader 类型,现在我们看一下如何使用它们

直接调用该 Reader 对象的 Read() 函数来读取数据

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

使用 ioutil.ReadAll 从一个 Reader 中读取数据,返回 raw []byte

b, err := ioutil.ReadAll(r)

使用 io.Copy() 从一个 Reader 中读取数据,并写入一个 Writer

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

使用 JSON Decoder 从一个 Reader 中直接 decode 数据

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

从一个已经 gzipped 了的 []byte 数据的 Reader 中创建一个 gzip.Reader

r = gzip.NewReader(r)

这样的话,从这个新创建的 reader 中读取数据的话就是已经自动 unzipped 的数据了。

设计 Reader 接口

在编写应用程序接口的时候,如果我们需要接受 []byte 或者 string 等类型的数据,我们可以把参数设置成接受 Reader 类型而不是 []byte 或 string,这样的话,我们的接口就能变得更加通用,适用的场景也会更多。

如下:

func Reverse(s string) (string, error)

可以变成

func Reverse(r io.Reader) io.Reader

这样改变之后,如果别人想要传入一个 string,那么它可以这样:

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

如果别人想要传入一个文件对象,可以这样:

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

或者,还可以传入一个 http.Request.Body

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

最后建议:

在任何需要处理数据流的地方,都应该尽可能使用 Reader/Writer。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2017-11-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏菩提树下的杨过

Silverlight:获取ContentTemplate中的命名控件

项目开发中遇到一个要求,需要将ComboBox右侧中的小三角箭头给去掉,通过Blend工具“编辑ComboBox的模板副本”得知,这是一个名为"BtnArrow...

22480
来自专栏函数式编程语言及工具

Akka(13): 分布式运算:Cluster-Sharding-运算的集群分片

    通过上篇关于Cluster-Singleton的介绍,我们了解了Akka为分布式程序提供的编程支持:基于消息驱动的运算模式特别适合分布式程序编程,我们不...

42980
来自专栏杨建荣的学习笔记

java中的序列化 (r4笔记第64天)

java中的序列化是一个很有意思的接口,只需要声明而无需做额外的工作,但是在虚拟机内部却做了大量的工作保证了这一特点。只要对象实现了序列化接口,就会把它转换为一...

33170
来自专栏ccylovehs

NoClassDefFoundError

在new JettisonMapperdXmlDriver()对象时 其构造器如下

51230
来自专栏菩提树下的杨过

silverlight中顺序/倒序异步加载多张图片

相册/图片切换广告等很多常用小应用中,服务器返回一组图片的uri,然后silverlight利用WebClient异步加载,如果要严格控制加载顺序的话,可以利用...

21970
来自专栏PingCAP的专栏

十分钟成为 Contributor 系列:重构内建函数进度报告

为了方便社区同学更好地参与 TiDB 项目,本文一方面对继上一篇文章发布后参考社区的反馈对表达式计算框架所做的修改进行详细介绍,另一方面对尚未重写的 built...

17200
来自专栏Flutter入门到实战

针对Android中类的重载方法很多时的几种优化解决方案

这个问题很多人在维护项目的时候可能会经常遇到,比如需求变更,刚开始只有一个构造方法,传入一个参数的。如下所示:

9220
来自专栏开发之途

Android 静默安装应用和静默卸载应用

33450
来自专栏闻道于事

Java实现word文档在线预览,读取office(word,excel,ppt)文件

10.7K70
来自专栏Java后端技术

复制指定源位置的多级文件夹下所有文件到指定目标位置

  3.遍历源位置文件夹下所有的文件,修改源位置为当前遍历项的文件位置,目标位置为刚刚上部创建的文件夹位置。

8610

扫码关注云+社区

领取腾讯云代金券