likes
comments
collection
share

深入理解 io.Writer 接口

作者站长头像
站长
· 阅读数 23

深入理解 io.Writer 接口

大家可能对 io.Writer 接口很陌生,日常中也不怎么关注,但是我们可能无意识中已经使用过了,比如:golang 文档中写文件的 例子,但是从这里我们没看到 io.Writer 接口的影子,原因是 os.WriteFile 方法内部已经实现了这个接口,为我们屏蔽了实现细节。

package main

import (
	"log"
	"os"
)

func main() {
	err := os.WriteFile("testdata/hello", []byte("Hello, Gophers!"), 0666)
	if err != nil {
		log.Fatal(err)
	}
}

另外多说一句,不少文档中推荐使用 ioutil.WriteFile 方法写文件,其实 golang 已经抛弃这个方法,推荐大家使用 os.WriteFile ,因为在 os.WriteFile 实现性能更好, 详情见这里

作为一名工程师,我们不仅需要知其然,更需要知其所以然,所以下面仔细分许下 io.Writer 接口。

io.Writer 接口的签名

type Writer interface {
    Write(p []byte) (n int, err error)
}

深入理解 io.Writer 接口

  • io.Writer 接口与 io.Reader 接口类似,只有一个方法: Write(p []byte) (n int, err error)

  • Write 方法从 p 中获取 len(p) 的字节数到底层数据流中,实际写入的数量返回到 n

  • 上文说过 io.Reader 接口是从不同来源中读取数据,然后复制数据到 buf 中,这个来源可能是文件、网络传输的数据或者普通的字符串,而 io.Writer 接口与之相反,io.Writer 会从 buf 中获取数据,然后再复制到底层数据流中,这里的底层数据流可能是文件、buffer或者网络响应。

io.Writer 接口具体应用

io.Writer 在写文件中应用

上面我们也说过,可以使用 os.WriteFile 写文件,这个方法是 go 已经封装好的,但是为了更加深入理解 io.Writer 接口,我们尝试使用原始的方法操作下。

深入理解 io.Writer 接口

package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.Create("file.txt")

	if err != nil {
		panic(err)
	}

	defer f.Close()

	n, err := f.Write([]byte("writing some string into the file using write method \n"))

	if err != nil {
		panic(err)
	}

	log.Printf("We write (%d bytes)", n)
}

  • 首先通过 os.Create 打开文件,如果文件不存在,创建文件;调用完成后,返回 f 变量,f 是 File 类型
  • f 是实现 io.Writer 接口的类型,会从 buf 中获取数据,然后写到底层数据流中(这里指文件),

感兴趣的可以看下 os.WriteFile 源码,其实就是把上面代码逻辑封装了下。

// WriteFile writes data to the named file, creating it if necessary.
// If the file does not exist, WriteFile creates it with permissions perm (before umask);
// otherwise WriteFile truncates it before writing, without changing permissions.
func WriteFile(name string, data []byte, perm FileMode) error {
	f, err := OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, perm)
	if err != nil {
		return err
	}
	_, err = f.Write(data)
	if err1 := f.Close(); err1 != nil && err == nil {
		err = err1
	}
	return err
}

io.Writer 在json encode中应用

json/encoding 的 NewEncoder 方法入参是 io.Writer 类型, 而 bytes.Buffer 是实现了 io.Writer 接口,通过 json/encoding 的 Encode 方法 JSON encoding 后,然后使用 bytes.Buffer 的 Write 方法写到层数据流中(这里指 buffer)

深入理解 io.Writer 接口

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
)

type user struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	buf := new(bytes.Buffer)
	u := user{
		Name: "zhangsan",
		Age:  20,
	}
	err := json.NewEncoder(buf).Encode(u)
	if err != nil {
		panic(err)
	}
	fmt.Print(buf.String())
}


程序运行结束后,返回结果为

{"name":"zhangsan","age":20}

另外我们可知,os.File 其实也实现了 io.Writer 接口,同样的原理,可以把 json/encoding 的 Encode 方法 JSON encoding 后,把数据写到文件中,这里的底层数据流是文件。

深入理解 io.Writer 接口

package main

import (
	"encoding/json"
	"os"
)

type user struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	f, err := os.Create("file.json")
	if err != nil {
		panic(err)
	}

	u := user{
		Name: "zhangsan",
		Age:  20,
	}
	err = json.NewEncoder(f).Encode(u)
	if err != nil {
		panic(err)
	}
}

总结

和 io.reader 接口类似,io.writer 接口在 golang 中地位也很重要,平常中感觉用不到是因为一些 util 的方法进行了封装。但是了解其基本原理我个人认为是必须的。所以,希望这篇文章可以帮助你理解它。

另外如果这篇文章帮助到了你,欢迎点赞和关注。