深入理解 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.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 接口,我们尝试使用原始的方法操作下。
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)
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 后,把数据写到文件中,这里的底层数据流是文件。
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 的方法进行了封装。但是了解其基本原理我个人认为是必须的。所以,希望这篇文章可以帮助你理解它。
另外如果这篇文章帮助到了你,欢迎点赞和关注。
转载自:https://juejin.cn/post/7120270272854229005