io 包为 I/O 原语 (primitives) 提供了基本的接口。
Golang 的标准库中已经有很多 I/O 原语的具体实现,比如 os.File 的 Read 和 Write 方法。io 包的主要作用是抽象出这些实现的函数功能,并将其封装成公共的接口。
由于接口涉及的底层操作依赖具体实现,因此不应该假定接口中的方法是并发安全的。
在 io 包中最重要的是两个接口:Reader 和 Writer 接口,只要满足这两个接口,就可以使用 IO 包的功能。因此,Golang 中的很多 IO 包都跟这两个接口有关。
Part1 Reader
Reader 接口设计细节
1
2
3
4
5
6
7
8
9
10
11
12
// io/io.go:77
type Reader interface {
Read(p []byte) (n int, err error)
}
// io/io.go:38
// EOF is the error returned by Read when no more input is available.
// Functions should return EOF only to signal a graceful end of input.
// If the EOF occurs unexpectedly in a structured data stream,
// the appropriate error is either ErrUnexpectedEOF or some other error
// giving more detail.
var EOF = errors.New("EOF")
细节 1
Read 函数将最多 len(p) 个字节读取到 p 中,并返回读取到的字节数 n(0 <= n <= len(p)) 和遇到的 err。
即使 Read 返回的 n < len(p),它也可能在调用过程中占用 len(p) 个字节作为暂存空间。
如果可读取的数据不足 len(p) 个字节,Read 一般会返回可用数据,而不会等待更多数据。
细节 2
当 Read 在成功读取 n > 0 个字节后遇到错误或 EOF,可以在本次调用返回一个 non-nil 错误。也可本次调用返回 nil,在下次调用再返回这个错误(且 n 为 0)。
例如当 n > 0 且读到输入末尾时,Read 可以返回 n, EOF 或 n, nil,并在下次调用 Read 时返回 0, EOF。但不建议在实现 Read 方法时返回 0, nil,开发者也不应该把 0, nil 当做 EOF 处理,除非 len(p)==0。
开发者在考虑 err 之前应该首先处理返回的 n > 0 个字节数据,这样可以正确应对成功读取若干个字节后遇到错误的情况,包括上文提到的 EOF 机制。
细节 3
所有实现都不应该保存切片 p。(Implementations must not retain p.)
Reader 接口的一些实现
实现 Reader 接口的常用类型有 os.File、strings.Reader、bufio.Reader、bytes.Buffer、bytes.Reader。
io 包本身的 LimitedReader、PipeReader、SectionReader 也实现了该接口。
下面,我们通过 ReadFrom 函数来了解该接口的具体用法。
1
2
3
4
5
6
7
8
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
}
1
2
3
4
5
6
7
8
// 从标准输入读取
data, err = ReadFrom(os.Stdin, 11)
// 从普通文件读取,其中 file 是 os.File 的实例
data, err = ReadFrom(file, 9)
// 从字符串读取
data, err = ReadFrom(strings.NewReader("from string"), 12)
os.Stdin
os.Stdin 是如何实现 Read 方法的呢?我们从源码中追溯一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// os/file.go:63
var (
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
// os/file_unix.go:87
func NewFile(fd uintptr, name string) *File {
kind := kindNewFile
if nb, err := unix.IsNonblock(int(fd)); err == nil && nb {
kind = kindNonBlock
}
return newFile(fd, name, kind)
}
// os/types.go:16
// File represents an open file descriptor.
type File struct {
*file // os specific
}
// os/file.go:112
// Read reads up to len(b) bytes from the File.
// It returns the number of bytes read and any error encountered.
// At end of file, Read returns 0, io.EOF.
func (f *File) Read(b []byte) (n int, err error) {
if err := f.checkValid("read"); err != nil {
return 0, err
}
n, e := f.read(b)
return n, f.wrapErr("read", e)
}
可以看到,os.Stdin 其实是 os.File 类型,而该类型实现了 io.Reader 接口。
Part2 Writer
Writer 接口
1
2
3
4
5
6
7
8
9
10
11
12
// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
Write(p []byte) (n int, err error)
}
Writer 接口的一些实现
fmt 包中,Fprint/Fprintf/Fprintln 接收一个 io.Writer 类型的参数,并把数据格式化输出到 io.Writer 中。
我们可以观察下标准库中是如何调用这些函数的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// fmt/print.go:262
// These routines end in 'ln', do not take a format string,
// always add spaces between operands, and add a newline
// after the last operand.
// Fprintln formats using the default formats for its operands and writes to w.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintln(a)
n, err = w.Write(p.buf)
p.free()
return
}
// fmt/print.go:273
// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
os.Stdout 同样也是 os.File 类型,该类型也实现了 io.Writer 接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// os/file.go:169
// Write writes len(b) bytes to the File.
// It returns the number of bytes written and an error, if any.
// Write returns a non-nil error when n != len(b).
func (f *File) Write(b []byte) (n int, err error) {
if err := f.checkValid("write"); err != nil {
return 0, err
}
n, e := f.write(b)
if n < 0 {
n = 0
}
if n != len(b) {
err = io.ErrShortWrite
}
epipecheck(f, e)
if e != nil {
err = f.wrapErr("write", e)
}
return n, err
}