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)}