Go语言中的io包全面解析

发表时间: 2024-05-21 18:10

Go 语言中, 为了方便开发者使用, 将 IO 操作封装在了如下几个包中:

-io 为 IO 原语(I/O primitives)提供基本的接口

-io/ioutil 封装一些实用的 I/O 函数

-fmt 实现格式化 I/O, 类似 C 语言中的 printf 和 scanf

-bufio 实现带缓冲I/O

在 Go 中, 输入和输出操作是使用原语实现的, 这些原语将数据模拟成可读的或可写的字节流。

为此, Go 的 io 包提供了 io.Reader 和 io.Writer 接口, 分别用于数据的输入和输出

io.Reader

io.Reader 表示一个读取器, 它将数据从某个资源读取到传输缓冲区。在缓冲区中, 数据可以被流式传输和使用。

对于要用作读取器的类型, 它必须实现 io.Reader 接口的唯一一个方法 Read(p []byte)。

换句话说,=, 只要实现了 Read(p []byte) , 那它就是一个读取器。

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

Read() 方法有两个返回值, 一个是读取到的字节数, 一个是发生错误时的错误。

同时, 如果资源内容已全部读取完毕, 应该返回 io.EOF 错误。

使用 Reader

利用 Reader 可以很容易地进行流式数据传输。Reader 方法内部是被循环调用的, 每次迭代, 它会从数据源读取一块数据放入缓冲区 p (即 Read 的参数 p)中, 直到返回 io.EOF 错误时停止。

下面是一个简单的例子, 通过 string.NewReader(string) 创建一个字符串读取器, 然后流式地按字节读取:

package mainimport (    "fmt"    "io"    "os"    "strings")func main() {    reader := strings.NewReader("Clear is better than clever")    p := make([]byte, 4)    for {        n, err := reader.Read(p)        if err != nil {        if err == io.EOF {        fmt.Println("EOF:", n)            break        }        fmt.Println(err)        os.Exit(1)    }    fmt.Println(n, string(p[:n]))    }}/*4 Clea4 r is4 bet4 ter4 than4 cle3 verEOF: 0*/

可以看到, 最后一次返回的 n 值有可能小于缓冲区大小。

io.Writer

io.Writer 表示一个编写器, 它从缓冲区读取数据, 并将数据写入目标资源。

对于要用作编写器的类型, 必须实现 io.Writer 接口的唯一一个方法 Write(p []byte)

同样,只要实现了 Write(p []byte) , 那它就是一个编写器。

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

Write() 方法有两个返回值, 一个是写入到目标资源的字节数, 一个是发生错误时的错误。

使用 Writer

标准库提供了许多已经实现了 io.Writer 的类型。

下面是一个简单的例子, 它使用 bytes.Buffer 类型作为 io.Writer 将数据写入内存缓冲区。

package mainimport (    "bytes"    "fmt"    "os")func main() {    proverbs := []string{    "Channels orchestrate mutexes serialize",    "Cgo is not Go",    "Errors are values",    "Don't panic",	}}

var writer bytes.Buffer //Buffer是一个实现了读写方法的可变大小的字节缓冲。本类型的零值是一个空的可用于读写的缓冲。

for _, p := range proverbs {    n, err := writer.Write([]byte(p))    if err != nil {        fmt.Println(err)        os.Exit(1)    }    if n != len(p) {        fmt.Println("failed to write data")        os.Exit(1)    }    }    fmt.Println(writer.String())}

输出打印的内容:

Channels orchestrate mutexes serializeCgo is not GoErrors are valuesDon't panic

io 包里其他有用的类型和方法

如前所述, Go标准库附带了许多有用的功能和类型, 让我们可以轻松使用流式io。

os.File

类型 os.File 表示本地系统上的文件。它实现了 io.Reader 和 io.Writer , 因此可以在任何 io 上下文中使用。

例如, 下面的例子展示如何将连续的字符串切片直接写入文件:

package mainimport (    "fmt"    "os")func main() {    proverbs := []string{        "Channels orchestrate mutexes serialize\n",        "Cgo is not Go\n",        "Errors are values\n",        "Don't panic\n",    }    file, err := os.Create("./proverbs.txt") //os.File    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()    for _, p := range proverbs {    // file 类型实现了 io.Writer    n, err := file.Write([]byte(p)) //实现写入    if err != nil {        fmt.Println(err)        os.Exit(1)    }    if n != len(p) {        fmt.Println("failed to write data")        os.Exit(1)    }    }		fmt.Println("file write done")}

同时, io.File 也可以用作读取器来从本地文件系统读取文件的内容。

例如, 下面的例子展示了如何读取文件并打印其内容:

package mainimport (    "fmt"    "io"    "os")func main() {    file, err := os.Open("./proverbs.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()    p := make([]byte, 4)    for {        n, err := file.Read(p)        if err == io.EOF {        break    }    fmt.Print(string(p[:n]))    }}

标准输入、输出和错误

os 包有三个可用变量 os.Stdout, os.Stdin 和 os.Stderr, 它们的类型为 *os.File, 分别代表 系统标准输入, 系统标准输出 和 系统标准错误 的文件句柄。

例如, 下面的代码直接打印到标准输出:

package mainimport ("fmt""os")func main() {    proverbs := []string{    "Channels orchestrate mutexes serialize\n",    "Cgo is not Go\n",    "Errors are values\n",    "Don't panic\n",    }    for _, p := range proverbs {        // 因为 os.Stdout 也实现了 io.Writer        n, err := os.Stdout.Write([]byte(p)) //输出到控制台        if err != nil {            fmt.Println(err)            os.Exit(1)        }        if n != len(p) {            fmt.Println("failed to write data")            os.Exit(1)        }    }}

io.Copy()

io.Copy() 可以轻松地将数据从一个 Reader 拷贝到另一个 Writer。

它抽象出 for 循环模式(我们上面已经实现了)并正确处理 io.EOF 和 字节计数。

下面是我们之前实现的简化版本

package mainimport (    "bytes"    "fmt"    "io"    "os")func main() {    proverbs := new(bytes.Buffer)    proverbs.WriteString("Channels orchestrate mutexes serialize\n")    proverbs.WriteString("Cgo is not Go\n")    proverbs.WriteString("Errors are values\n")    proverbs.WriteString("Don't panic\n")    file, err := os.Create("./proverbs.txt")    if err != nil {        fmt.Println(err)        os.Exit(1) //Exit让当前程序以给出的状态码code退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行。    }    defer file.Close()    // io.Copy 完成了从 proverbs 读取数据并写入 file 的流程    if _, err := io.Copy(file, proverbs); err != nil { //写入        fmt.Println(err)        os.Exit(1)    }    fmt.Println("file created")}

那么, 我们也可以使用 io.Copy() 函数重写从文件读取并打印到标准输出的先前程序, 如下所示:

package mainimport (    "fmt"    "io"    "os")func main() {    file, err := os.Open("./proverbs.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()    if _, err := io.Copy(os.Stdout, file); err != nil { //读取        fmt.Println(err)        os.Exit(1)    }}

io.WriteString()

此函数让我们方便地将字符串类型写入一个 Writer:

package mainimport (    "fmt"    "io"    "os")func main() {    file, err := os.Create("./magic_msg.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()    if _, err := io.WriteString(file, "Go is fun!"); err != nil {        fmt.Println(err)        os.Exit(1)    }}

使用管道的 Writer 和 Reader

类型 io.PipeWriter 和 io.PipeReader 在内存管道中模拟 io 操作。

数据被写入管道的一端, 并使用单独的 goroutine 在管道的另一端读取。

下面使用 io.Pipe() 创建管道的 reader 和 writer, 然后将数据从 proverbs 缓冲区复制到io.Stdout :

package mainimport (    "bytes"    "io"    "os")func main() {    proverbs := new(bytes.Buffer) //开辟一个缓冲区    proverbs.WriteString("Channels orchestrate mutexes serialize\n")    proverbs.WriteString("Cgo is not Go\n")    proverbs.WriteString("Errors are values\n")    proverbs.WriteString("Don't panic\n")    piper, pipew := io.Pipe()    // 将 proverbs 写入 pipew 这一端    go func() {        defer pipew.Close()        io.Copy(pipew, proverbs)    }()    // 从另一端 piper 中读取数据并拷贝到标准输出    io.Copy(os.Stdout, piper)    piper.Close()}

缓冲区 io

标准库中 bufio 包支持 缓冲区 io 操作, 可以轻松处理文本内容。

例如, 以下程序逐行读取文件的内容, 并以值 '\n' 分隔:

package mainimport (    "bufio"    "fmt"    "io"    "os")func main() {    file, err := os.Open("./planets.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()    reader := bufio.NewReader(file)    for {        line, err := reader.ReadString('\n')        if err != nil {            if err == io.EOF {                break            } else {                fmt.Println(err)                os.Exit(1)            }   			 }    		fmt.Print(line)		}}

ioutil

io 包下面的一个子包 utilio 封装了一些非常方便的功能

例如, 下面使用函数 ReadFile 将文件内容加载到 []byte 中。

package mainimport (    "fmt"    "io/ioutil"    "os")func main() {    bytes, err := ioutil.ReadFile("./planets.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    fmt.Printf("%s", bytes)}