go中的读写

本文介绍了Go标准库中的IO接口,重点解析了io.Reader和io.Writer。文章通过实例展示了如何使用这两个接口进行文件读写,并指出许多标准库工具函数都是基于这两个接口实现的。此外,还探讨了高级接口设计,如gzip包中Reader和Writer的实现方式,以及如何自定义类型实现高级的读写接口。
摘要由CSDN通过智能技术生成

其实go标准库中不少工具包都有各自涉及io操作的实现,但是归根到底还是基于io包的几个底层io接口,如io.Reader、io.byteReader等。其中:

io包主要提供了底层字节流io接口。

io/ioutil包提供了针对文件读写的io接口。

strings包提供了针对字符串的简单io接口。

gzip包实现了编解码的io接口。

crypto/cipher包也提供了加密用的io接口。

os包提供了平台无关的操作系统的接口,包括文件io。

基本io接口

io.Reader:

文件读写是最基本的底层能力,go的标准包io定义了一个Reader接口,只包含一个Read方法:

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

就跟node中通过buffer一次次中转数据流一样,Read方法接收一个byte类型切片(相当于buffer),长度由用户定义。

该方法每次读取len(p)字节的数据,并返回整数n和err实例。n的值为每次读取的字节数。

假如我要读的字符串长为14,传入的切片p长度为8,则:

第一次执行Read方法会读满8字节,并返回8,<nil>。

第二次执行read方法只会读取剩下的6个字节,剩余的填充0,返回6,<nil>。

第三次执行Read方法会返回0,EOF。这个EOF的错误用来告诉我们文件已读取完毕。

一个A Tour of Go的示例如下:

package main
import (
	"fmt"
	"io"  //io包提供一个EOF,等价于main中文件读取完时返回的err.EOF
	"strings"
)
func main() {
    //strings.NewReader返回一个类型,该类型保存接收的字符串并实现了Read方法
	r := strings.NewReader("Hello, Reader!")
	b := make([]byte, 8)
    //通过for循环不断读取Reader中的数据
	for {
		n, err := r.Read(b)
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
				if err == io.EOF {
			break
		}
	}
}
//输出:
n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""

上边通过一个for循环一直执行Read方法,直到字符串读取完。此时再执行一次Read方法,会返回EOF错误告知你已经读取到文件末尾了。

io.Writer:

有io.Reader当然也有io.Writer:

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

同io.Read,Write方法每次将len(p)个字节写入指定的数据流中。当n<len(p)时返回error告知文件已写完。

----分界线呀分界线---

其实go标准io包中定义了一堆基本io接口,之所以着重提上面两个接口,是因为go的标准库里,许多读写、操作文件相关工具函数,都是基于这两个接口的实现,包括gzipiostringsos等等。所以熟知这两个接口对理解标准库工具的实现非常有用!!!

(今天看源码看到眼睛疼,哭。。)

 

下边是展开的探讨》》》

高级接口设计

前边介绍io.Read的示例代码中有这两行:

import (
    "strings"
) 
...  
    //strings.NewReader接收一个字符串并创建一个reader对象
    r := strings.NewReader("Hello, Reader!")

strings是一个字符串工具包,其中strings.NewReader用于创建一个Reader类型实例,其源码很简单:

//定义一个strings包自己的Reader类型
type Reader struct {
	s        string
	i        int64 // current reading index
	prevRune int   // index of previous rune; or < 0
}
...
//对io.Reader接口的实现
func (r *Reader) Read(b []byte) (n int, err error) {
	if r.i >= int64(len(r.s)) {
		return 0, io.EOF
	}
	r.prevRune = -1
	n = copy(b, r.s[r.i:])
	r.i += int64(n)
	return
}
...
//对外暴露一个NewReader方法,该方法返回一个Reader类型的实例的指针
func NewReader(s string) *Reader { return &Reader{s, 0, -1} }

在strings包里,只是自定义一个Reader类型,并给该类型实现Reader接口。

但标准库中更普遍的io接口实现模式是(以Reader为例):

0.定义一个模块自己的Reader类型。

1.该自定义类型接收一个io.Reader实例作为自己的一个字段。

2.给该自定义类型实现高级的读写接口。

3.定义一个函数,接收一个io.Reader实例并返回一个自定义Reader类型实例。

比如下面是gzip.NewReader对Reader的实现:

//Reader的r字段挂载一个flate.Reader接口
//flate.Reader接口是{io.reader,io.ByteReader}组合接口
type Reader struct {
	Header       // valid after NewReader or Reader.Reset
	r            flate.Reader
	decompressor io.ReadCloser
    ...
}
//接收一个底层的Reader接口,挂载到自定义Reader类型内部,并返回该类型
func NewReader(r io.Reader) (*Reader, error) {}
//给自定义类型实现一个Reader接口
func (z *Reader) Read(p []byte) (n int, err error) {}

gzip.NewWriter也是类似的实现。

先看看gzip的使用,这里引用stackoverflow的举例:

// use something like this to compress:
var b bytes.Buffer
w := gzip.NewWriter(&b)
w.Write([]byte("hello, world\n"))
w.Close()
...
//And this to unpack:
r, err := gzip.NewReader(&b)
io.Copy(os.Stdout, r)
r.Close()

可以看到,使用标准包的gzip压缩/解压文件时,也是创建一个Writer/Reader类型来处理数据。

我们只需要关注前两行代码。

第一行定义的bytes.Buffer类型,我看了下源码,是实现了Writer接口的,即变量b是一个io.Writer。

func (b *Buffer) Write(p []byte) (n int, err error) {
	b.lastRead = opInvalid
	m, ok := b.tryGrowByReslice(len(p))
	if !ok {
		m = b.grow(len(p))
	}
	return copy(b.buf[m:], p), nil
}

然后我们看看gzip的源码

预定义一些压缩相关的常量:

const (
	NoCompression      = flate.NoCompression
	BestSpeed          = flate.BestSpeed
	BestCompression    = flate.BestCompression
	DefaultCompression = flate.DefaultCompression
	HuffmanOnly        = flate.HuffmanOnly
)

然后定义一个gzip自己的Writer类型 

//  w用来保存传入的io.Writer
//  其他是一些和压缩相关的标记常量
type Writer struct {
	Header      // written at first call to Write, Flush, or Close
	w           io.Writer
	level       int
	wroteHeader bool
	compressor  *flate.Writer
	digest      uint32 // CRC-32, IEEE polynomial (section 8)
	size        uint32 // Uncompressed size (section 2.3.1)
	closed      bool
	buf         [10]byte
	err         error
}

对外暴露一个gzip.NewWriter方法:

//DefaultCompression是gzip预定义的一个表示压缩值的整数
func NewWriter(w io.Writer) *Writer {
	z, _ := NewWriterLevel(w, DefaultCompression) 
	return z
}

这个方法把我们传给gzip.NewWriter的byte.Buffer传给另一个函数NewWriterLevel,并返回一个实例z。

我们看看NewWriterLevel做了什么:

func NewWriterLevel(w io.Writer, level int) (*Writer, error) {
	if level < HuffmanOnly || level > BestCompression {
		return nil, fmt.Errorf("gzip: invalid compression level: %d", level)
	}
	z := new(Writer)
	z.init(w, level)
	return z, nil
}

它new了一个Writer类型作为z返回。所以z也就是这个东西:

type Writer struct {
    Header      // written at first call to Write, Flush, or Close
    w           io.Writer
    level       int
    ....
}

然后这个gzip.Writer类型也实现了io.Writer接口:

func (z *Writer) Write(p []byte) (int, error) {
	if z.err != nil {
		return 0, z.err
	}
	var n int
	// Write the GZIP header lazily.
	if !z.wroteHeader {
	    ...
	}
//更新压缩相关的标记量
	z.size += uint32(len(p))
	z.digest = crc32.Update(z.digest, crc32.IEEETable, p)
//压缩后返回(int, error)
	n, z.err = z.compressor.Write(p)
	return n, z.err
}

go的io包里有个专门的ioutil包,提供了一些针对文件操作的函数,如ReadFile等,看了下其源码,最终还是用了bytes包的buffer类型的read方法,这个Read方法正是对io.Reader接口的实现。

 

事实上,go的标准库都遵循了统一的风格,或者说做了一样的事情:

*定义接口

*定义类型并为之添加方法

*对外暴露某些函数,而这些函数要么执行特定运算要么返回某个自定义类型

 

以至于我觉得,如果我们要构建自己的go库,我们只要遵循同样的方式即可,好清爽。。。

go标准库那一堆包,看到我头疼。但是看多了之后发现go还是蛮简洁的。。。至少比js的好看多了

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值