Go 语言标准库之 io 包

io 包提供了对I/O原语的基本接口,该包的基本任务是包装这些原语已有的实现(如 os 包里的原语),使之成为共享的公共接口,这些公共接口抽象出了泛用的函数并附加了一些相关的原语的操作。

常用接口

Reader/Writer 接口

Reader 接口

// Reader 接口用于包装基本的读取方法
type Reader interface {
    Read(p []byte) (n int, err error)
}

len(p)个字节读取到 p 中,返回写入的字节数 n 和遇到的任何错误 err。即使 Read 方法返回的n < len(p),在调用过程也会占用len(p)个字节作为暂存空间。若可读取的数据不足len(p)个字节,Read 方法会返回可用数据,而不是等待更多数据。

当 Read 方法读取n > 0个字节后遇到一个错误或 EOF (end-of-file),会返回读取的字节数,同时在该次调用返回一个非 nil 错误或者在下一次的调用时返回 0 和该错误。一般情况下,Reader 接口会在输入流的结尾返回非 0 的字节数,返回值 err 为 nil 或者io.EOF,但下次调用必然返回(0, io.EOF)。调用者在考虑错误之前应当首先处理n > 0字节的返回数据,这样做可以正确地处理在读取一些字节后产生的I/O错误,同时允许 EOF 的出现。

☕️ 示例代码

package main

import (
    "fmt"
    "io"
    "os"
    "strings"
)

func ReadFrom(reader io.Reader, num int) ([]byte, error) {
    p := make([]byte, num)
    n, err := reader.Read(p)
    if n > 0 {
        return p[:n], nil
    }
    return p, err
}

func main() {
    // 例子 1:从标准输入中读取 11 个字节
    // 控制台输入:hello world!!!!
    data, err := ReadFrom(os.Stdin, 11)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", data) // hello world

    // 例子 2:从普通文件中读取 9 个字节
    // 文件内容:hello world!!!!
    f, err := os.Open("./test.txt")
    if err != nil {
        panic(err)
    }
    defer f.Close()
    data, err = ReadFrom(f, 9)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", data) // hello wor

    // 例子 3:从字符串中读取 12 个字节
    r := strings.NewReader("hello world!!!!")
    data, err = ReadFrom(r, 12)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", data) // hello world!
}

Writer 接口

// Writer 接口用于包装基本的写入方法
type Writer interface {
    Write(p []byte) (n int, err error)
}

len(p) 字节数据从 p 写入底层的数据流,返回从写入的字节数 n 和遇到的任何错误 err。如果 Write 方法返回的n < len(p),它就必须返回一个 非 nil 的错误。Write 方法不能修改切片 p 中的数据,即使临时修改也不行。

⭐️ 示例代码

package main

import (
    "fmt"
    "io"
    "os"
)

func WriteTo(write io.Writer, p []byte) (int, error) {
    n, err := write.Write(p)
    return n, err
}

func main() {
    p := []byte("hello world!!!\n")

    // 例子 1:将字符串写入到标准输出
    n, err := WriteTo(os.Stdout, p)
    if err != nil {
        panic(err)
    }
    fmt.Printf("write %d bytes\n", n)

    // 例子 2:将字符串写入文件中
    f, err := os.Create("./test.txt")
    if err != nil {
        panic(err)
    }
    defer f.Close()
    n, err = WriteTo(f, p)
    if err != nil {
        panic(err)
    }
    fmt.Printf("write %d bytes\n", n)
}

// 控制台输出:
// hello world!!!
// write 15 bytes
// write 15 bytes

// test.txt 文件内容:
// hello world!!!

Reader/Writer 接口实现类型

  • os.File同时实现了 Reader 和 Writer 接口
  • strings.Reader实现了 Reader 接口
  • bufio.Readerbufio.Writer 分别实现了 Reader 和 Writer 接口
  • bytes.Buffer同时实现了 Reader 和 Writer 接口
  • bytes.Reader实现了 Reader 接口
  • compress/gzip.Readercompress/gzip.Writer分别实现了 Reader 和 Writer 接口
  • crypto/cipher.StreamReadercrypto/cipher.StreamWriter分别实现了 Reader 和 Writer 接口
  • crypto/tls.Conn同时实现了 Reader 和 Writer 接口
  • encoding/csv.Readerencoding/csv.Writer分别实现了 Reader 和 Writer 接口
  • mime/multipart.Part 实现了 Reader 接口
  • net/conn分别实现了 Reader 和 Writer 接口(Conn 接口定义了 Read 和 Write 方法)

除此之外,io 包本身也有这两个接口的实现类型。如:

  • 实现了 Reader 的类型:LimitedReaderPipeReaderSectionReader
  • 实现了 Writer 的类型:PipeWriter

以上类型中,常用的类型有:os.Filestrings.Readerbufio.Reader/Writerbytes.Bufferbytes.Reader


ReaderAt/WriterAt 接口

ReaderAt 接口

// ReaderAt 接口包装了基本的 ReadAt 方法
type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error)
}

从底层输入流的偏移量 off 位置读取len(p)字节数据写入 p,返回读取的字节数 n 和遇到的任何错误 err。当 ReadAt 方法返回的n < len(p)时,它会返回一个非 nil 的错误来说明没有读取更多的字节的原因。在这方面,ReadAt 方法是比 Read 方法要严格的。

即使 ReadAt 方法返回的n < len(p),在调用过程也会占用len(p)个字节作为暂存空间。如果有部分可用数据,但不够len(p)字节,ReadAt 方法会阻塞直到获取len(p)个字节数据或者遇到错误。在这方面,ReadAt 方法和 Read 方法是不同的。如果 ReadAt 方法返回时到达输入流的结尾,返回的n == len(p),返回的 err 为 nil 或者io.EOF

如果 ReadAt 方法是从某个有偏移量的底层输入流读取,ReadAt 方法既不应影响底层的偏移量,也不被它所影响。ReadAt 方法的调用者可以对同一输入流执行并行的 ReadAt 调用。

✏️ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    reader := strings.NewReader("Go语言中文网")
    p := make([]byte, 6)
    n, err := reader.ReadAt(p, 2)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Read %d bytes: %s\n", n, p) // Read 6 bytes: 语言
}

WriterAt 接口

// WriterAt 接口包装了基本的 WriteAt 方法
type WriterAt interface {
    WriteAt(p []byte, off int64) (n int, err error)
}

len(p)个字节数据写入偏移量 off 位置的底层数据流中,返回写入的字节数 n 和遇到的任何错误 err。当 WriteAt 方法返回的n < len(p)时,它就必须返回一个非 nil 的错误。

如果 WriteAt 方法写入的目标是某个有偏移量的底层输出流,WriteAt 方法既不应影响底层的偏移量,也不被它所影响。ReadAt 方法的调用者可以对同一输入流执行并行的 WriteAt 调用。(前提是写入范围不重叠)

上一篇:04-MyBatisPlus条件构造器


下一篇:评测三款最流行的epub阅读器