0%

go io.Writer 接口与 io.reader 接口

io.Writerio.Reader 应该是最常用的两个接口了,一个负责向我们自己实现的类型写入数据,一个负责我们自己实现的的类型对外写出数据。

简单的说接口就是约定,遵循约定我们可以先使用然后再进行实现,这就是接口的意义。个人感觉与泛化能力有关。

io.Wreter

这里我先讲 io.Writer 接口,因为我个人觉得 io.wreier 要简单的。
我们先看一下 io.Writer 的定义 https://golang.org/pkg/io/#Writer

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

这里我简单的翻译一下

Writer 是包装基本 Write 方法的接口。
Writeplen(p)个字节写入基础数据流。 它返回从 p(0 <= n <= len(p)) 写入的字节数,以及遇到的任何导致写入提前停止的错误。 如果写返回 n < len(p),则必须返回一个非 nil 错误。 写操作不得修改切片数据,即使是临时的。
实现不得保留p

简单的解释就是 io.Writer 接口就是写入了多少数据就返回写数据的长度以及过程中可能发生的错误,在写入过程中不允许修改输入的原始数据,聪明的你看到这句可能就会想到 io.Reader 是从那里输出数据的了,这里先不说 io.Reader 稍后再说。

接下来我们就实现一个 io.Writer 接口

package main

import (
    "fmt"
    "io"
)

type Test struct {
    data []byte // 容纳数据的地方
}

// Write io.Writer 接口
// 实现接口 我反正是直接从接口定义中复制处出来的 当然也可以按这个格式手写
func (t *Test) Write(p []byte) (n int, err error) {
    oldLen := len(t.data)
    t.data = append(t.data, p...)
    newLen := len(t.data)

    n = newLen - oldLen
    return
}

// String 格式化输出
func (t *Test) String() string {
    if t.data == nil {
        return "<nil>"
    }
    return string(t.data)
}

func main() {
    t := &Test{} // 声明实例
    str := "niconiconi"
    fmt.Println(len([]byte(str)))         // 写入数据的长度
    n, err := fmt.Fprint(t, "niconiconi") // 调用 io.Writer 写入数据
    fmt.Println(n, err)                   // 实际写入数据的长度,以及可能发生的错误,当然我在定义 io.Writer 的时候是没有定义可能发生的错误的

    fmt.Println(t) // 输出刚刚写入的数据
}

这样我们就实现了一个最简单的接口,因为 io.Writer 是向我们的数据类型写入数据,所以 我们直接决定如何使用数据,这里我直接使用 fmt.Println 进行输出了

io.Reader

io.Reader 接口是用于当前类型向外输出数据的
这里我们看一下啊 io.Reader 的定义 https://golang.org/pkg/io/#Reader

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.

中文翻译

Reader 是包装基本 Read 方法的接口。

读取最多将 len(p) 个字节读入 p 。它返回读取的字节数(0 <= n <= len(p))和遇到的任何错误。即使 Read 返回 n < len(p),也可能在调用过程中将所有 p 用作临时空间。如果某些数据可用,但不是 len(p) 个字节,按常规,Read 将返回可用数据,而不是等待更多数据。

成功读取 n > 0 个字节后,当 Read 遇到错误或文件结束条件时,它将返回读取的字节数。它可能从同一调用返回 (non-nil) 错误,或者从后续调用返回错误 (and n == 0)。这种一般情况的一个实例是,读取器在输入流的末尾返回非零字节数的情况可能返回 err == EOFerr == nil 。下一次读取应返回0EOF

在考虑错误 err 之前,调用者应始终处理返回的 n > 0 个字节。这样做可以正确处理在读取某些字节后发生的 I/O 错误,以及两种允许的 EOF 行为。

不鼓励 Read 的实现不返回零字节计数且错误为 nil ,除非 len(p)== 0 除外。调用者应将返回值 0nil 视为没有任何反应;特别是它并不表示 EOF

实现不得保留 p

这个接口比 io.Writer 要复杂一点 简单的说 p 有多大就最多写入多大的数据,如果遇到错误就返回写入的字节数以及错误。如果一次没有度完下次读取继续剩下的部分,全部读完后错误 返回 io.EOF

接下来我们就来实现一下

package main

import (
    "fmt"
    "io"
)

// 参考 strings.NewReader()

type Reader struct {
    data   string // 转载的数据
    offset int64  // 当前读取进度
}

// Read 实现 io.Reader 接口
func (r *Reader) Read(b []byte) (n int, err error) {
    if r.offset >= int64(len(r.data)) { // 如果已读取长度大于接收长度者直接读取结束(从逻辑上讲就意味者结束)
        return 0, io.EOF
    }

    n = copy(b, r.data[r.offset:]) // 接着上次结束的地方进行读取
    r.offset += int64(n)           // 记录读取数据量
    return
}

// New 新建接口类型
func New(s string) *Reader {
    return &Reader{ // 初始化接口原始类型
        data:   s,
        offset: 0,
    }
}

func main() {
    //reader := New("niconiconi")        // 新建接口类型
    //res, err := ioutil.ReadAll(reader) // 从 io.Reader 接口读取全部数据
    //fmt.Println(string(res), err)      // 输出读取到的结果

    // OR
    // 这是第二种读取方式

    str := "niconiconi" // 原始数据
    reader := New(str)  // 新建接口类型
    // 创建一个大小刚好的 []byte 接收数据 当然你也可以创建一个比原始数据更大的 []byte 来接收数据
    // 如果我们创建的 []byte 没有原始数据大的话可以多次进行读取直到读取到 io.EOF 为止具体怎么读就看里需求了
    out := make([]byte, len(str))
    n, err := reader.Read(out)
    fmt.Println(n, err)
    fmt.Println(string(out))

    //out2 := make([]byte, 1)
    //n2, err2 := reader.Read(out2)
    //fmt.Println(n2, err2, out2) //如果读完了继续读就能读取到 io.EOF
}

这样我们就实现了一个 io.Reader 接口

篇外

如果觉得这两个接口不好理解的话,可以理解为,以我们正在使用的数据类型为中心 io.Writer 对内输入数据 io.Reader 对外输出数据

就算我们不会实现接口,也可以通过组合(理解不了可以理解为继承)其它实现了对应方法的数据类型获取接口,比如 io.ReadWrider 就可以从 bytes.buffer 获得。