go语言标准库--io包

328 阅读11分钟

io 包为 I/O 原语提供了基本的接口。它主要包装了这些原语的已有实现。

由于这些被接口包装的I/O原语是由不同的低级操作实现,因此,在另有声明之前不该假定它们的并发执行是安全的。

在 io 包中最重要的是两个接口:Reader 和 Writer 接口,只要满足这两个接口,它就可以使用 IO 包的功能。

1. Reader 接口

Reader 接口的定义如下:

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

Read 将 len(p) 个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len(p)) 以及任何遇到的错误。即使 Read 返回的 n < len(p),它也会在调用过程中占用 len(p) 个字节作为暂存空间。若可读取的数据不到 len(p) 个字节,Read 会返回可用数据,而不是等待更多数据。

当 Read 在成功读取 n > 0 个字节后遇到一个错误或 EOF (end-of-file),它会返回读取的字节数。它可能会同时在本次的调用中返回一个non-nil错误,或在下一次的调用中返回这个错误(且 n 为 0)。 一般情况下, Reader会返回一个非0字节数n, 若 n = len(p) 个字节从输入源的结尾处由 Read 返回,Read可能返回 err == EOF 或者 err == nil。并且之后的 Read() 都应该返回 (n:0, err:EOF)。

2. Writer 接口

Writer 接口的定义如下:

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

Write 将 len(p) 个字节从 p 中写入到基本数据流中。它返回从 p 中被写入的字节数 n(0 <= n <= len(p))以及任何遇到的引起写入提前停止的错误。若 Write 返回的 n < len(p),它就必须返回一个 非nil 的错误。

3. ReaderAt 和 WriterAt 接口

ReaderAt 接口的定义如下:

type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error)
}

ReadAt 从基本输入源的偏移量 off 处开始,将 len(p) 个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len(p))以及任何遇到的错误。

WriterAt 接口的定义如下:

type WriterAt interface {
    WriteAt(p []byte, off int64) (n int, err error)
}

WriteAt 从 p 中将 len(p) 个字节写入到偏移量 off 处的基本数据流中。它返回从 p 中被写入的字节数 n(0 <= n <= len(p))以及任何遇到的引起写入提前停止的错误。若 WriteAt 返回的 n < len(p),它就必须返回一个 非nil 的错误。

若 WriteAt 携带一个偏移量写入到目标中,WriteAt 应当既不影响偏移量也不被它所影响。

若被写区域没有重叠,可对相同的目标并行执行 WriteAt 调用。

4. ReaderFrom 和 WriterTo 接口

ReaderFrom 的定义如下:

type ReaderFrom interface {
    ReadFrom(r Reader) (n int64, err error)
}

官方文档中关于该接口方法的说明:

ReadFrom 从 r 中读取数据,直到 EOF 或发生错误。其返回值 n 为读取的字节数。除 io.EOF 之外,在读取过程中遇到的任何错误也将被返回。

如果 ReaderFrom 可用,Copy 函数就会使用它。

注意:ReadFrom 方法不会返回 err == EOF。

WriterTo的定义如下:

type WriterTo interface {
    WriteTo(w Writer) (n int64, err error)
}

WriteTo 将数据写入 w 中,直到没有数据可写或发生错误。其返回值 n 为写入的字节数。 在写入过程中遇到的任何错误也将被返回。

如果 WriterTo 可用,Copy 函数就会使用它

5. Seeker 接口

接口定义如下:

type Seeker interface {
    Seek(offset int64, whence int) (ret int64, err error)
}

Seek 设置下一次 Read 或 Write 的偏移量为 offset,它的解释取决于 whence: 0 表示相对于文件的起始处,1 表示相对于当前的偏移,而 2 表示相对于其结尾处。 Seek 返回新的偏移量和一个错误,如果有的话。

也就是说,Seek 方法是用于设置偏移量的,这样可以从某个特定位置开始操作数据流。听起来和 ReaderAt/WriteAt 接口有些类似,不过 Seeker 接口更灵活,可以更好的控制读写数据流的位置。

简单的示例代码:获取倒数第二个字符(需要考虑 UTF-8 编码,这里的代码只是一个示例)

reader := strings.NewReader("Go语言中文网")
reader.Seek(-6, io.SeekEnd)
r, _, _ := reader.ReadRune()
fmt.Printf("%c\n", r)

image.png

小贴士

whence 的值,在 io 包中定义了相应的常量,应该使用这些常量

const (
  SeekStart   = 0 // seek relative to the origin of the file
  SeekCurrent = 1 // seek relative to the current offset
  SeekEnd     = 2 // seek relative to the end
)

6. Closer接口

接口定义如下:

type Closer interface {
    Close() error
}

该接口比较简单,只有一个 Close() 方法,用于关闭数据流。

文件 (os.File)、归档(压缩包)、数据库连接、Socket 等需要手动关闭的资源都实现了 Closer 接口。

实际编程中,经常将 Close 方法的调用放在 defer 语句中。

7.ByteReader 和 ByteWriter

这组接口的用途:读或写一个字节。接口定义如下:

type ByteReader interface {
    ReadByte() (c byte, err error)
}

type ByteWriter interface {
    WriteByte(c byte) error
}

在标准库中,有如下类型实现了 io.ByteReader 或 io.ByteWriter:

  • bufio.Reader/Writer 分别实现了io.ByteReader 和 io.ByteWriter
  • bytes.Buffer 同时实现了 io.ByteReader 和 io.ByteWriter
  • bytes.Reader 实现了 io.ByteReader
  • strings.Reader 实现了 io.ByteReader

8.ByteScanner、RuneReader 和 RuneScanner

将这三个接口放在一起,是考虑到与 ByteReader 相关或相应。

ByteScanner 接口的定义如下:

type ByteScanner interface {
    ByteReader
    UnreadByte() error
}

可见,它内嵌了 ByteReader 接口(可以理解为继承了 ByteReader 接口),UnreadByte 方法的意思是:将上一次 ReadByte 的字节还原,使得再次调用 ReadByte 返回的结果和上一次调用相同,也就是说,UnreadByte 是重置上一次的 ReadByte。注意,UnreadByte 调用之前必须调用了 ReadByte,且不能连续调用 UnreadByte。即:

buffer := bytes.NewBuffer([]byte{'a', 'b'})
err := buffer.UnreadByte()

buffer := bytes.NewBuffer([]byte{'a', 'b'})
buffer.ReadByte()
err := buffer.UnreadByte()
err = buffer.UnreadByte()

err 都 非nil,错误为:bytes.Buffer: UnreadByte: previous operation was not a read

RuneReader 接口和 ByteReader 类似,只是 ReadRune 方法读取单个 UTF-8 字符,返回其 rune 和该字符占用的字节数。该接口在 regexp 包有用到。

9. SectionReader 类型

SectionReader 是一个 struct(没有任何导出的字段),实现了 Read, Seek 和 ReadAt,同时,内嵌了 ReaderAt 接口。结构定义如下:

type SectionReader struct {
    r     ReaderAt    // 该类型最终的 Read/ReadAt 最终都是通过 r 的 ReadAt 实现
    base  int64        // NewSectionReader 会将 base 设置为 off
    off   int64        // 从 r 中的 off 偏移处开始读取数据
    limit int64        // limit - off = SectionReader 流的长度
}

从名称我们可以猜到,该类型读取数据流中部分数据。看一下

func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader

的文档说明就知道了:

NewSectionReader 返回一个 SectionReader,它从 r 中的偏移量 off 处读取 n 个字节后以 EOF 停止。

也就是说,SectionReader 只是内部(内嵌)ReaderAt 表示的数据流的一部分:从 off 开始后的 n 个字节。

这个类型的作用是:方便重复操作某一段 (section) 数据流;或者同时需要 ReadAt 和 Seek 的功能。

10. LimitedReader 类型

LimitedReader 结构定义如下:

type LimitedReader struct {
    R Reader // underlying reader,最终的读取操作通过 R.Read 完成
    N int64  // max bytes remaining
}

从 R 读取但将返回的数据量限制为 N 字节。每调用一次 Read 都将更新 N 来反应新的剩余数量。

也就是说,最多只能返回 N 字节数据。

使用示例如下:

content := "This Is LimitReader Example"
reader := strings.NewReader(content)
limitReader := &io.LimitedReader{R: reader, N: 8}
for limitReader.N > 0 {
    tmp := make([]byte, 2)
    limitReader.Read(tmp)
    fmt.Printf("%s", tmp)
}

输出:

This Is

可见,通过该类型可以达到 只允许读取一定长度数据 的目的。

在 io 包中,LimitReader 函数的实现其实就是调用 LimitedReader:

func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }

11. PipeReader 和 PipeWriter 类型

PipeReader(一个没有任何导出字段的 struct)是管道的读取端。它实现了 io.Reader 和 io.Closer 接口。结构定义如下:

type PipeReader struct {
    p *pipe
}

关于 PipeReader.Read 方法的说明:从管道中读取数据。该方法会堵塞,直到管道写入端开始写入数据或写入端被关闭。如果写入端关闭时带有 error(即调用 CloseWithError 关闭),该Read返回的 err 就是写入端传递的error;否则 err 为 EOF。

PipeWriter(一个没有任何导出字段的 struct)是管道的写入端。它实现了 io.Writer 和 io.Closer 接口。结构定义如下:

type PipeWriter struct {
    p *pipe
}

关于 PipeWriter.Write 方法的说明:写数据到管道中。该方法会堵塞,直到管道读取端读完所有数据或读取端被关闭。如果读取端关闭时带有 error(即调用 CloseWithError 关闭),该Write返回的 err 就是读取端传递的error;否则 err 为 ErrClosedPipe。

使用示例如下:

func main() {
    pipeReader, pipeWriter := io.Pipe()
    go PipeWrite(pipeWriter)
    go PipeRead(pipeReader)
    time.Sleep(30 * time.Second)
}

func PipeWrite(writer *io.PipeWriter){
    data := []byte("Go语言中文网")
    for i := 0; i < 3; i++{
        n, err := writer.Write(data)
        if err != nil{
            fmt.Println(err)
            return
        }
        fmt.Printf("写入字节 %d\n",n)
    }
    writer.CloseWithError(errors.New("写入段已关闭"))
}

func PipeRead(reader *io.PipeReader){
    buf := make([]byte, 128)
    for{
        fmt.Println("接口端开始阻塞5秒钟...")
        time.Sleep(5 * time.Second)
        fmt.Println("接收端开始接受")
        n, err := reader.Read(buf)
        if err != nil{
            fmt.Println(err)
            return
        }
        fmt.Printf("收到字节: %d\n buf内容: %s\n",n,buf)
    }
}

io.Pipe() 用于创建一个同步的内存管道 (synchronous in-memory pipe),函数签名:

func Pipe() (*PipeReader, *PipeWriter)

它将 io.Reader 连接到 io.Writer。一端的读取匹配另一端的写入,直接在这两端之间复制数据;它没有内部缓存。它对于并行调用 Read 和 Write 以及其它函数或 Close 来说都是安全的。一旦等待的 I/O 结束,Close 就会完成。并行调用 Read 或并行调用 Write 也同样安全:同种类的调用将按顺序进行控制。

正因为是同步的,因此不能在一个 goroutine 中进行读和写。

另外,对于管道的 close 方法(非 CloseWithError 时),err 会被置为 EOF。

12. Copy 和 CopyN 函数

Copy 函数的签名:

func Copy(dst Writer, src Reader) (written int64, err error)

函数文档:

Copy 将 src 复制到 dst,直到在 src 上到达 EOF 或发生错误。它返回复制的字节数,如果有错误的话,还会返回在复制时遇到的第一个错误。

成功的 Copy 返回 err == nil,而非 err == EOF。由于 Copy 被定义为从 src 读取直到 EOF 为止,因此它不会将来自 Read 的 EOF 当做错误来报告。

若 dst 实现了 ReaderFrom 接口,其复制操作可通过调用 dst.ReadFrom(src) 实现。此外,若 src 实现了 WriterTo 接口,其复制操作可通过调用 src.WriteTo(dst) 实现。

CopyN 函数的签名:

func CopyN(dst Writer, src Reader, n int64) (written int64, err error)

函数文档:

CopyN 将 n 个字节(或到一个error)从 src 复制到 dst。 它返回复制的字节数以及在复制时遇到的最早的错误。当且仅当err == nil时,written == n 。

若 dst 实现了 ReaderFrom 接口,复制操作也就会使用它来实现。

13. ReadAtLeast 和 ReadFull 函数

ReadAtLeast 函数的签名:

func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)

ReadAtLeast 将 r 读取到 buf 中,直到读了最少 min 个字节为止。它返回复制的字节数,如果读取的字节较少,还会返回一个错误。若没有读取到字节,错误就只是 EOF。如果一个 EOF 发生在读取了少于 min 个字节之后,ReadAtLeast 就会返回 ErrUnexpectedEOF。若 min 大于 buf 的长度,ReadAtLeast 就会返回 ErrShortBuffer。对于返回值,当且仅当 err == nil 时,才有 n >= min。

一般可能不太会用到这个函数。使用时需要注意返回的 error 判断。

ReadFull 函数的签名:

func ReadFull(r Reader, buf []byte) (n int, err error)

函数文档:

ReadFull 精确地从 r 中将 len(buf) 个字节读取到 buf 中。它返回复制的字节数,如果读取的字节较少,还会返回一个错误。若没有读取到字节,错误就只是 EOF。如果一个 EOF 发生在读取了一些但不是所有的字节后,ReadFull 就会返回 ErrUnexpectedEOF。对于返回值,当且仅当 err == nil 时,才有 n == len(buf)。

注意该函数和 ReadAtLeast 的区别:ReadFull 将 buf 读满;而 ReadAtLeast 是最少读取 min 个字节。

14. WriteString 函数

这是为了方便写入 string 类型提供的函数,函数签名:

func WriteString(w Writer, s string) (n int, err error)

函数文档:

WriteString 将s的内容写 入w中,当 w 实现了 WriteString 方法时,会直接调用该方法,否则执行 w.Write([]byte(s))。

15. MultiReader 和 MultiWriter 函数

这两个函数的定义分别是:

func MultiReader(readers ...Reader) Reader
func MultiWriter(writers ...Writer) Writer

它们接收多个 Reader 或 Writer,返回一个 Reader 或 Writer。我们可以猜想到这两个函数就是操作多个 Reader 或 Writer 就像操作一个。

事实上,在 io 包中定义了两个非导出类型:mutilReader 和 multiWriter,它们分别实现了 io.Reader 和 io.Writer 接口。类型定义为:

type multiReader struct {
    readers []Reader
}

type multiWriter struct {
    writers []Writer
}

对于这两种类型对应的实现方法(Read 和 Write 方法)的使用,我们通过例子来演示。

MultiReader 的使用

readers := []io.Reader{
    strings.NewReader("from strings reader"),
    bytes.NewBufferString("from bytes buffer"),
}
reader := io.MultiReader(readers...)
data := make([]byte, 0, 128)
buf := make([]byte, 10)

for n, err := reader.Read(buf); err != io.EOF ; n, err = reader.Read(buf){
    if err != nil{
        panic(err)
    }
    data = append(data,buf[:n]...)
}
fmt.Printf("%s\n", data)

输出:

from strings readerfrom bytes buffer

代码中首先构造了一个 io.Reader 的 slice,由 strings.Reader 和 bytes.Buffer 两个实例组成,然后通过 MultiReader 得到新的 Reader,循环读取新 Reader 中的内容。从输出结果可以看到,第一次调用 Reader 的 Read 方法获取到的是 slice 中第一个元素的内容……也就是说,MultiReader 只是逻辑上将多个 Reader 组合起来,并不能通过调用一次 Read 方法获取所有 Reader 的内容。在所有的 Reader 内容都被读完后,Reader 会返回 EOF。

MultiWriter 的使用

file, err := os.Create("tmp.txt")
if err != nil {
    panic(err)
}
defer file.Close()
writers := []io.Writer{
    file,
    os.Stdout,
}
writer := io.MultiWriter(writers...)
writer.Write([]byte("Go语言中文网"))

这段程序执行后在生成 tmp.txt 文件,同时在文件和屏幕中都输出:Go语言中文网。这和 Unix 中的 tee 命令类似。

16. TeeReader函数

函数签名如下:

func TeeReader(r Reader, w Writer) Reader

TeeReader 返回一个 Reader,它将从 r 中读到的数据写入 w 中。所有经由它处理的从 r 的读取都匹配于对应的对 w 的写入。它没有内部缓存,即写入必须在读取完成前完成。任何在写入时遇到的错误都将作为读取错误返回。

也就是说,我们通过 Reader 读取内容后,会自动写入到 Writer 中去。例子代码如下:

reader := io.TeeReader(strings.NewReader("Go语言中文网"), os.Stdout)
reader.Read(make([]byte, 20))

输出结果:

Go语言中文网

这种功能的实现其实挺简单,无非是在 Read 完后执行 Write。