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.Reader
和bufio.Writer
分别实现了 Reader 和 Writer 接口 -
bytes.Buffer
同时实现了 Reader 和 Writer 接口 -
bytes.Reader
实现了 Reader 接口 -
compress/gzip.Reader
和compress/gzip.Writer
分别实现了 Reader 和 Writer 接口 -
crypto/cipher.StreamReader
和crypto/cipher.StreamWriter
分别实现了 Reader 和 Writer 接口 -
crypto/tls.Conn
同时实现了 Reader 和 Writer 接口 -
encoding/csv.Reader
和encoding/csv.Writer
分别实现了 Reader 和 Writer 接口 -
mime/multipart.Part
实现了 Reader 接口 -
net/conn
分别实现了 Reader 和 Writer 接口(Conn 接口定义了 Read 和 Write 方法)
除此之外,io 包本身也有这两个接口的实现类型。如:
- 实现了 Reader 的类型:
LimitedReader
、PipeReader
、SectionReader
- 实现了 Writer 的类型:
PipeWriter
以上类型中,常用的类型有:os.File
、strings.Reader
、bufio.Reader/Writer
、bytes.Buffer
、bytes.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 调用。(前提是写入范围不重叠)