# 输入输出

# io

定义 I/O 基本接口,及相关组合。

●Reader, Writer, Closer, Seeker
●ReadWriter, ReaderAt, WriterAt, ReaderFrom, WriterTo
●ReadCloser, WriteCloser, ReadWriteCloser
●ByteReader, ByteWriter, StringWriter
package main
import (
	"bufio"
	"log"
	"os"
)
func main() {
	f, err := os.Create("./demo.txt")
	if err != nil {
		log.Fatalln(err)
	}
	defer f.Close()
	defer f.Sync()
	w := bufio.NewWriter(f)
	defer w.Flush()
	w.WriteString("hello, world!")
}

源码解析

# Reader

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

所有实现了 Read 方法的类型都满足 io.Reader 接口,也就是说,在所有需要 io.Reader 的地方,可以传递实现了 Read () 方法的类型的实例。

demo:

func main() {
	// 从标准输入读取
	// data, err = ReadFrom(os.Stdin, 11)
	// 从普通文件读取,其中 file 是 os.File 的实例
	// data, err = ReadFrom(file, 9)
	// 从字符串读取
	data, err := ReadFrom(strings.NewReader("from string"), 12)
	fmt.Println(data, err)
}
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
}

ReadFrom 函数将 io.Reader 作为参数,也就是说,ReadFrom 可以从任意的地方读取数据,只要来源实现了 io.Reader 接口。比如,我们可以从标准输入、文件、字符串等读取数据,

# Writer

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

同样的,所有实现了 Write 方法的类型都实现了 io.Writer 接口

demo: fmt.Println

// Fprintln 使用其参数默认格式进行格式化并写入 w.
// 参数之间始终添加空格并附加换行符。
// 它返回写入的字节数以及遇到的任何写入错误。
func Fprintln(w io.Writer, a ...any) (n int, err error) {
	p := newPrinter()
	p.doPrintln(a)
	n, err = w.Write(p.buf)
	p.free()
	return
}
// Println 使用其参数的默认格式进行格式化并写入标准输出。
// 参数之间始终添加空格并附加换行符。
// 它返回写入的字节数以及遇到的任何写入错误。
func Println(a ...any) (n int, err error) {
	return Fprintln(os.Stdout, a...)
}

os.File 同时实现了这 io.Reader 接口和 io.Writer 接口。我们还看到 os.Stdin/Stdout 这样的代码,它们似乎分别实现了 io.Reader/io.Writer 接口。没错,实际上在 os 包中有这样的代码:

var (
    Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
    Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
    Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

也就是说,Stdin/Stdout/Stderr 只是三个特殊的文件类型的标识(即都是 os.File 的实例),自然也实现了 io.Reader 和 io.Writer。

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

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

实现了 Reader 的类型:LimitedReader、PipeReader、SectionReader
实现了 Writer 的类型:PipeWriter

以上类型中,常用的类型有:os.File、strings.Reader、bufio.Reader/Writer、bytes.Buffer、bytes.Reader

从接口名称很容易猜到,一般地, Go 中接口的命名约定:接口名以 er 结尾。注意,这里并非强行要求,你完全可以不以 er 结尾。标准库中有些接口也不是以 er 结尾的。

# ReaderAt, WriterAt

type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error)
}

ReaderAt 接口使得可以从指定偏移量处开始读取数据。

demo:

func main() {
	reader := strings.NewReader("01twelve test")
	p := make([]byte, 6)
	n, err := reader.ReadAt(p, 2)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s, %d\n", p, n)
}
// twelve, 6
type WriterAt interface {
    WriteAt(p []byte, off int64) (n int, err error)
}

WriterAt 接口将数据写入到数据流的特定偏移量之后。

demo :

func main() {
	file, err := os.Create("demo.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	file.WriteString("Golang__12")
	n, err := file.WriteAt([]byte("twelve"), 8)
	if err != nil {
		panic(err)
	}
	fmt.Println(n)
}
// Golang__twelve

# ReaderFrom, WriterTo

type ReaderFrom interface {
    ReadFrom(r Reader) (n int64, err error)
}

ReadFrom 从 r 中读取数据,直到 EOF 或发生错误。其返回值 n 为读取的字节数。除 io.EOF 之外,在读取过程中遇到的任何错误也将被返回。

demo:

func main() {
	file, err := os.Open("demo.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	writer := bufio.NewWriter(os.Stdout)
	writer.ReadFrom(file)
	writer.Flush()
}
type WriterTo interface {
    WriteTo(w Writer) (n int64, err error)
}

WriteTo 将数据写入 w 中,直到没有数据可写或发生错误。其返回值 n 为写入的字节数。 在写入过程中遇到的任何错误也将被返回。

func main() {
	reader := bytes.NewReader([]byte("golang 666"))
	reader.WriteTo(os.Stdout)
}

# Seeker

type Seeker interface {
    Seek(offset int64, whence int) (ret int64, err error)
}

Seek 方法是用于设置偏移量的,这样可以从某个特定位置开始操作数据流。

听起来和 ReaderAt/WriteAt 接口有些类似,不过 Seeker 接口更灵活,可以更好的控制读写数据流的位置。

demo

func main() {
	reader := strings.NewReader("Golang语言")
	reader.Seek(-6, io.SeekEnd)
	r, _, _ := reader.ReadRune()
	fmt.Printf("%c\n", r)
}
// 语

whence 的值,在 io 包中定义了相应的常量,应该使用这些常量

const (
  SeekStart   = 0 // seek relative to the origin of the file
  SeekCurrent = 1 // seek relative to the current offset
  SeekEnd     = 2 // seek relative to the end
)

# Closer

type Closer interface {
    Close() error
}

该接口比较简单,只有一个 Close () 方法,用于关闭数据流。

文件 (os.File)、归档(压缩包)、数据库连接、Socket 等需要手动关闭的资源都实现了 Closer 接口。

实际编程中,经常将 Close 方法的调用放在 defer 语句中。

错误示范:

func main() {
	file, err := os.Open("demo2.txt")
	defer file.Close() // should check returned error before deferring file.Close() (SA5001)go-staticcheck
	if err != nil {
		fmt.Println(err)
	}
}

# PipeReader, PipeWriter

//pipe 是 PipeReader 和 PipeWriter 底层的共享管道结构。
type pipe struct {
	wrMu sync.Mutex // Serializes Write operations
	wrCh chan []byte
	rdCh chan int
	once sync.Once // Protects closing done
	done chan struct{}
	rerr onceError
	werr onceError
}

type PipeReader struct {
    p *pipe
}

PipeReader(一个没有任何导出字段的 struct)是管道的读取端。它实现了 io.Reader 和 io.Closer 接口。

从管道中读取数据。该方法会堵塞,直到管道写入端开始写入数据或写入端被关闭。如果写入端关闭时带有 error(即调用 CloseWithError 关闭),该 Read 返回的 err 就是写入端传递的 error;否则 err 为 EOF。


type PipeWriter struct {
    p *pipe
}

写数据到管道中。该方法会堵塞,直到管道读取端读完所有数据或读取端被关闭。如果读取端关闭时带有 error(即调用 CloseWithError 关闭),该 Write 返回的 err 就是读取端传递的 error;否则 err 为 ErrClosedPipe。


func Pipe() (*PipeReader, *PipeWriter) {
	p := &pipe{
		wrCh: make(chan []byte),
		rdCh: make(chan int),
		done: make(chan struct{}),
	}
	return &PipeReader{p}, &PipeWriter{p}
}

用于创建一个同步的内存管道 (synchronous in-memory pipe)

它将 io.Reader 连接到 io.Writer。一端的读取匹配另一端的写入,直接在这两端之间复制数据;它没有内部缓存。它对于并行调用 Read 和 Write 以及其它函数或 Close 来说都是安全的。一旦等待的 I/O 结束,Close 就会完成。并行调用 Read 或并行调用 Write 也同样安全:同种类的调用将按顺序进行控制。

正因为是同步的,因此不能在一个 goroutine 中进行读和写。

另外,对于管道的 close 方法(非 CloseWithError 时),err 会被置为 EOF。

demo:

func main() {
	pipeReader, pipeWriter := io.Pipe()
	go PipeWrite(pipeWriter)
	go PipeRead(pipeReader)
	time.Sleep(30 * time.Second)
}
func PipeWrite(writer *io.PipeWriter) {
	data := []byte("golang 12345")
	for i := 0; i < 3; i++ {
		n, err := writer.Write(data)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Printf("写入字节 %d\n", n)
	}
	writer.CloseWithError(errors.New("写入段已关闭"))
}
func PipeRead(reader *io.PipeReader) {
	buf := make([]byte, 128)
	for {
		fmt.Println("接口端开始阻塞5秒钟...")
		time.Sleep(5 * time.Second)
		fmt.Println("接收端开始接受")
		n, err := reader.Read(buf)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Printf("收到字节: %d\n buf内容: %s\n", n, buf)
	}
}

# Copy, CopyN

func Copy(dst Writer, src Reader) (written int64, err error)

Copy 将 src 复制到 dst,直到在 src 上到达 EOF 或发生错误。它返回复制的字节数,如果有错误的话,还会返回在复制时遇到的第一个错误。

成功的 Copy 返回 err == nil,而非 err == EOF。由于 Copy 被定义为从 src 读取直到 EOF 为止,因此它不会将来自 Read 的 EOF 当做错误来报告。

若 dst 实现了 ReaderFrom 接口,其复制操作可通过调用 dst.ReadFrom (src) 实现。此外,若 src 实现了 WriterTo 接口,其复制操作可通过调用 src.WriteTo (dst) 实现。

demo:

func main() {
	io.Copy(os.Stdout, strings.NewReader("Golang 1234"))
}

func CopyN(dst Writer, src Reader, n int64) (written int64, err error)

CopyN 将 n 个字节 (或到一个 error) 从 src 复制到 dst。 它返回复制的字节数以及在复制时遇到的最早的错误。当且仅当 err == nil 时,written == n 。

若 dst 实现了 ReaderFrom 接口,复制操作也就会使用它来实现。

demo:

func main() {
	io.CopyN(os.Stdout, strings.NewReader("Golang_see you"), 6)
}
// Golang

# ioutil

废弃,相关功能迁移至 io 和 os

# bufio

平台无关的缓冲 I/O,提升读写效率。

package main
import (
	// "bufio"
	"io"
	"os"
)
func main() {
	f, _ := os.Open("./tmp.dat")
	defer f.Close()
	var r io.Reader = f
	// r = bufio.NewReaderSize(r, 8192)
	for {
		buf := make([]byte, 512)
		 _, err := r.Read(buf)
		if err == io.EOF { break }
	}
}
/*
$ dd if=/dev/random of=tmp.dat bs=1M count=100
$ strace ./test 2>&1 | grep "read" | wc -l
*/

# Reader

// bufio/bufio.go
const (
	defaultBufSize = 4096 // 默认缓存大小
)
var (
	ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
	ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
	ErrBufferFull        = errors.New("bufio: buffer full")
	ErrNegativeCount     = errors.New("bufio: negative count")
)
// Reader 继承了  io.Reader 并且实现了
type Reader struct {
	buf          []byte
	rd           io.Reader // reader provided by the client
	r, w         int       // 缓存读写的位置
	err          error
	lastByte     int // 最后一次读到的字节;-1 means invalid
	lastRuneSize int // 最后一次读到的 Rune 的大小;-1 means invalid
}
const minReadBufferSize = 16
const maxConsecutiveEmptyReads = 100

实例化

新建 Reader

func NewReaderSize(rd io.Reader, size int) *Reader {
	// Is it already a Reader?
	b, ok := rd.(*Reader)
	if ok && len(b.buf) >= size {
		return b
	}
	if size < minReadBufferSize {
		size = minReadBufferSize
	}
	r := new(Reader)
	r.reset(make([]byte, size), rd)
	return r
}
func NewReader(rd io.Reader) *Reader {
	return NewReaderSize(rd, defaultBufSize)
}

Read 操作

实现 io 的接口

func (b *Reader) Read(p []byte) (n int, err error)
    func (b *Reader) ReadByte() (c byte, err error)
    func (b *Reader) ReadRune() (r rune, size int, err error)
    func (b *Reader) UnreadByte() error
    func (b *Reader) UnreadRune() error
    func (b *Reader) WriteTo(w io.Writer) (n int64, err error)

新的接口

func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
func (b *Reader) ReadBytes(delim byte) ([]byte, error)
func (b *Reader) ReadString(delim byte) (string, error)
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)

底层都是调用的 ReadSlice 方法

func (b *Reader) ReadSlice(delim byte) (line []byte, err error) {
	s := 0 // search start index
	for {
		// Search buffer.
		if i := bytes.IndexByte(b.buf[b.r+s:b.w], delim); i >= 0 {
			i += s
			line = b.buf[b.r : b.r+i+1]
			b.r += i + 1
			break
		}
		// Pending error?
		if b.err != nil {
			line = b.buf[b.r:b.w]
			b.r = b.w
			err = b.readErr()
			break
		}
		// Buffer full?
		if b.Buffered() >= len(b.buf) {
			b.r = b.w
			line = b.buf
			err = ErrBufferFull
			break
		}
		s = b.w - b.r // do not rescan area we scanned before
		b.fill() // buffer is not full
	}
	// Handle last byte, if any.
	if i := len(line) - 1; i >= 0 {
		b.lastByte = int(line[i])
		b.lastRuneSize = -1
	}
	return
}
func main() {
	reader := bufio.NewReader(strings.NewReader("aabbccdd. \n1122"))
	line, _ := reader.ReadSlice('\n')
	fmt.Printf("the line:%s\n", line)
	n, _ := reader.ReadSlice('\n')
	fmt.Printf("the line:%s\n", line)
	fmt.Println(string(n))
}
// the line:aabbccdd.
// the line:1122ccdd.
// 1122

注意返回的切片是对 b.buf 的引用,所以两次打印 line 不一样

填充操作 读一块进入缓存

// fill reads a new chunk into the buffer.
func (b *Reader) fill() {
	// Slide existing data to beginning.
	if b.r > 0 {
		copy(b.buf, b.buf[b.r:b.w])
		b.w -= b.r
		b.r = 0
	}
	if b.w >= len(b.buf) {
		panic("bufio: tried to fill full buffer")
	}
	// Read new data: try a limited number of times.
	for i := maxConsecutiveEmptyReads; i > 0; i-- {
		n, err := b.rd.Read(b.buf[b.w:])
		if n < 0 {
			panic(errNegativeRead)
		}
		b.w += n
		if err != nil {
			b.err = err
			return
		}
		if n > 0 {
			return
		}
	}
	b.err = io.ErrNoProgress
}

# Writer

type Writer struct {
	err error
	buf []byte
	n   int
	wr  io.Writer
}

实例化

func NewWriterSize(w io.Writer, size int) *Writer {
	// Is it already a Writer?
	b, ok := w.(*Writer)
	if ok && len(b.buf) >= size {
		return b
	}
	if size <= 0 {
		size = defaultBufSize
	}
	return &Writer{
		buf: make([]byte, size),
		wr:  w,
	}
}
func NewWriter(w io.Writer) *Writer {
	return NewWriterSize(w, defaultBufSize)
}

Write 操作

// 返回缓存中有多少字节可用
func (b *Writer) Available() int { return len(b.buf) - b.n }
// 返回缓存中已经写入了多少字节
func (b *Writer) Buffered() int { return b.n }
func (b *Writer) Write(p []byte) (nn int, err error) {
    // 待写入数据量大于缓存区剩余空间,多次完成。
	for len(p) > b.Available() && b.err == nil {
		var n int
		if b.Buffered() == 0 {
			// Large write, empty buffer.
			// Write directly from p to avoid copy.
			n, b.err = b.wr.Write(p)
		} else {
            // 填满剩余缓存空间
			n = copy(b.buf[b.n:], p)
			b.n += n
			b.Flush()
		}
		nn += n
		p = p[n:]
	}
	if b.err != nil {
		return nn, b.err
	}
	n := copy(b.buf[b.n:], p)
	b.n += n
	nn += n
	return nn, nil
}
// 将缓冲区数据 Flush 给底层 Writer。清空缓冲区。
func (b *Writer) Flush() error {
	if b.err != nil {
		return b.err
	}
	if b.n == 0 {
		return nil
	}
	n, err := b.wr.Write(b.buf[0:b.n])
	if n < b.n && err == nil {
		err = io.ErrShortWrite
	}
	if err != nil {
        // 部分写入。剩余数据前移。
		if n > 0 && n < b.n {
			copy(b.buf[0:b.n-n], b.buf[n:b.n])
		}
		b.n -= n
		b.err = err
		return err
	}
     // 全部写入。
	b.n = 0
	return nil
}

# ReadWriter

// ReadWriter 结构存储了 bufio.Reader 和 bufio.Writer 类型的指针(内嵌),它实现了 io.ReadWriter 结构。
type ReadWriter struct {
	*Reader
	*Writer
}
// NewReadWriter allocates a new ReadWriter that dispatches to r and w.
func NewReadWriter(r *Reader, w *Writer) *ReadWriter {
	return &ReadWriter{r, w}
}

# Scanner

type Scanner struct {
	r            io.Reader // The reader provided by the client.
	split        SplitFunc // The function to split the tokens.
	maxTokenSize int       // Maximum size of a token; modified by tests.
	token        []byte    // Last token returned by split.
	buf          []byte    // Buffer used as argument to split.
	start        int       // First non-processed byte in buf.
	end          int       // End of data in buf.
	err          error     // Sticky error.
	empties      int       // Count of successive empty tokens.
	scanCalled   bool      // Scan has been called; buffer is in use.
	done         bool      // Scan has finished.
}

实例化

func NewScanner(r io.Reader) *Scanner {
	return &Scanner{
		r:            r,
		split:        ScanLines,
		maxTokenSize: MaxScanTokenSize,
	}
}

SplitFunc

type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)

SplitFunc 定义了 用于对输入进行分词的 split 函数的签名。
参数 data 是还未处理的数据,atEOF 标识 Reader 是否还有更多数据(是否到了 EOF)。
返回值 advance 表示从输入中读取的字节数,token 表示下一个结果数据,err 则代表可能的错误。

Split

通过 Split 方法为 Scanner 实例设置分词, Scanner 实例的默认 split 总是 ScanLines,如果我们想要用其他的 split,可以通过 Split 方法做到。注意,我们应该在调用 Scan 方法之前调用 Split 方法。

func main() {
	const input = "This is The Golang Standard Library.\nWelcome you!"
	scanner := bufio.NewScanner(strings.NewReader(input))
	scanner.Split(bufio.ScanWords)
	count := 0
	for scanner.Scan() {
		count++
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading input:", err)
	}
	fmt.Println(count) // 8
}

demo:

func main() {
	file, err := os.Create("demo.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	file.WriteString("Golang__twelve .\n hello .\n  welcome!")
	// 将文件 offset 设置到文件开头
	file.Seek(0, io.SeekStart)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
}
更新于
-->