目录

go 的文件 IO

这个系列我们开始学习 Go 语言的第五个部分: 库。 接下来我们就从 Go 的文件 I/O 说起。

1. 文件 IO 概述

1.1 标准库的读写模型

Go基于io.Writer和io.Reader这两个简单的接口类型构建了图57-1所示的Go标准库读写模型。

/images/go/expert/io_model.png

1.2 包含 I/O 操作的标准库

Go 的标准库我们就从文件 IO 开始。Go 标准库中为文件 IO 提供了如下这些包:

  1. os: 文件操作的方法都在 os 包中
  2. io:
    • 为了扩展"文本操作"的范围(类似 Linux 中一切接文件,可以把文本操作扩展到其他类型的资源上) io 包提供了I/O原语的基本接口
    • io 包基本任务是包装这些原语已有的实现(如os包里的原语),使之成为共享的公共接口。
  3. bufio:
    • os 包内的文件IO 是不带语言层的缓存的, bufio 提供了语言层带缓存 IO
    • 通过带缓存 IO,使得我们可以以特定的方式读取类文件中的内容,比如按特定分隔符读取等等
  4. string/bytes:为了以类文件方式操作 string 和 []bytes ,string 和 bytes 包为 string 和 bytes 实现了部分文件 io 的公共接口
  5. io/ioutil: 提供了一些文件 IO 的便捷函数

接下来我们就一一来介绍这些包的使用。

2. os 文件操作

os包提供了操作系统函数的不依赖平台的接口。os包的接口规定为在所有操作系统中都是一致的,非公用的属性可以从操作系统特定的syscall包获取。os 包中与文件 IO 相关的部分如下:

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 文件对象
type File struct {
    // 内含隐藏或非导出字段
}

type File
	func Create(name string) (file *File, err error) 
	func Open(name string) (file *File, err error)
	// 文件操作的核心函数
	func OpenFile(name string, flag int, perm FileMode) (file *File, err error) 
	func NewFile(fd uintptr, name string) *File
	func Pipe() (r *File, w *File, err error)

	func (f *File) Name() string
	func (f *File) Stat() (fi FileInfo, err error)
	func (f *File) Fd() uintptr
	func (f *File) Chdir() error   // Chdir将当前工作目录修改为f,f必须是一个目录
	func (f *File) Chmod(mode FileMode) error
	func (f *File) Chown(uid, gid int) error
	func (f *File) Readdir(n int) (fi []FileInfo, err error)
	func (f *File) Readdirnames(n int) (names []string, err error)
	func (f *File) Truncate(size int64) error
	func (f *File) Sync() (err error)

	// 重要: 文件 IO 公共接口包含的方法
	func (f *File) Read(b []byte) (n int, err error)
	// off 表示相对于文件开始 off 位置起
	func (f *File) ReadAt(b []byte, off int64) (n int, err error)
	func (f *File) Write(b []byte) (n int, err error)
	func (f *File) WriteString(s string) (ret int, err error)
	func (f *File) WriteAt(b []byte, off int64) (n int, err error)
	func (f *File) Seek(offset int64, whence int) (ret int64, err error)
	func (f *File) Close() error

// 文件状态对象
type FileInfo interface {
    Name() string       // 文件的名字(不含扩展名)
    Size() int64        // 普通文件返回值表示其大小;其他文件的返回值含义各系统不同
    Mode() FileMode     // 文件的模式位
    ModTime() time.Time // 文件的修改时间
    IsDir() bool        // 等价于Mode().IsDir()
    Sys() interface{}   // 底层数据来源(可以返回nil)
}

type FileInfo
	func Stat(name string) (fi FileInfo, err error)
	func Lstat(name string) (fi FileInfo, err error)

// FileMode代表文件的模式和权限位
type FileMode uint32

type FileMode
	func (m FileMode) IsDir() bool
	func (m FileMode) IsRegular() bool
	func (m FileMode) Perm() FileMode
	func (m FileMode) String() string

os 包为文件 IO 提供了这样几个核心对象:

  1. File:
    • 代表一个打开的文件对象
    • File 中提供了几个文件读写方法就是 io 包抽象的公共接口
  2. FileInfo: 描述一个文件对象
  3. FileMode: 代表文件的模式和权限位

3. io 抽象的公共接口

3.1 io 中的接口

io 包将文件 IO 中所有的文件操作及其组合都抽象为独立的接口,其接口大致上分成了三类:

  1. os 包文件操作及其组合
  2. 带读取撤回的接口
  3. 文件互操作
 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 1. 基础 io 操作及其组合
type Reader interface {
	Read(p []byte) (n int, err error)
}

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

type ReadCloser
type ReadSeeker
type WriteCloser
type WriteSeeker
type ReadWriter
type ReadWriteCloser
type ReadWriteSeeker

// 2. 可撤回读
type ByteWriter
type ByteReader
type ByteScanner
type RuneReader
type RuneScanner

type ByteWriter interface {
    WriteByte(c byte) error
}

type ByteReader interface {
    ReadByte() (c byte, err error)
}

type ByteScanner interface {
    ByteReader
	//读回撤
	UnreadByte() error
}

// 3. 文件互操作
type ReaderFrom
type WriterTo

// ReadFrom方法从r读取数据直到EOF或者遇到错误。返回值n是读取的字节数,执行时遇到的错误(EOF除外)
type ReaderFrom interface {
    ReadFrom(r Reader) (n int64, err error)
}

// WriteTo方法将数据写入w直到没有数据可以写入或者遇到错误。返回写入的字节数和执行时遇到的任何错误
type WriterTo interface {
    WriteTo(w Writer) (n int64, err error)
}

3.2 io 中的数据结构

除了接口外,io 包还提供了一些具有特殊用途的数据结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. 只能限制读取 n 个字节的 Reader
type LimitedReader
	func LimitReader(r Reader, n int64) Reader
	func (l *LimitedReader) Read(p []byte) (n int, err error)

// 2. 从 r 中的偏移量off处为起始,读取n个字节后以EOF停止的SectionReader
type SectionReader
	func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader
	func (s *SectionReader) Size() int64
	func (s *SectionReader) Read(p []byte) (n int, err error)
	func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error)
	func (s *SectionReader) Seek(offset int64, whence int) (int64, error)

// 3. 管道符
type PipeReader
	func Pipe() (*PipeReader, *PipeWriter)
	func (r *PipeReader) Read(data []byte) (n int, err error)
	func (r *PipeReader) Close() error
	func (r *PipeReader) CloseWithError(err error) error

type PipeWriter
	func (w *PipeWriter) Write(data []byte) (n int, err error)
	func (w *PipeWriter) Close() error
	func (w *PipeWriter) CloseWithError(err error) error

3.3 io 中的功能函数

除了接口和数据结构,io 包还包含了如下具有特殊用途的功能函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 返回一个从 r 读,并且往 w 写的 Reader
func TeeReader(r Reader, w Writer) Reader

// 将多个 Reader 合并为一个 Reader
func MultiReader(readers ...Reader) Reader

// 将写同时写入多个 writer
func MultiWriter(writers ...Writer) Writer

// 文件拷贝
func Copy(dst Writer, src Reader) (written int64, err error)

// 文件拷贝,限制长度为 n
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)

// 至少读取min字节数据填充进buf
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)

// 精确地读取len(buf)字节数据填充进buf
func ReadFull(r Reader, buf []byte) (n int, err error)

// 将字符串s的内容写入w中
func WriteString(w Writer, s string) (n int, err error)

4. io/ioutil

说完了 io 包,我们先来看看 io/ioutil,这个包也是提供了一些 io 的功能函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 写黑洞,/dev/null 所有Write调用都会无实际操作的成功返回
var Discard io.Writer = devNull(0)

// 用一个无操作的Close方法包装r返回一个ReadCloser接口
func NopCloser(r io.Reader) io.ReadCloser

// r 读取数据直到EOF或遇到error,返回读取的数据和遇到的错误, EOF 错误不会被返回
func ReadAll(r io.Reader) ([]byte, error)

// 同 ReadAll,接收的是一个文件路径
func ReadFile(filename string) ([]byte, error)

// 向filename指定的文件中写入数据,文件不存在创建,文件存在清空
func WriteFile(filename string, data []byte, perm os.FileMode) error

// 返回dirname指定的目录的目录信息的有序列表,与 os.File Readdir 方法作用一致
func ReadDir(dirname string) ([]os.FileInfo, error)

// 在dir目录里创建一个新的、使用prfix作为前缀的临时文件夹,并返回文件夹的路径
// 如果dir是空字符串,TempDir使用默认用于临时文件的目录(即 os.TempDir函数返回的临时目录)
func TempDir(dir, prefix string) (name string, err error)

// 在dir目录下创建一个新的、使用prefix为前缀的临时文件,以读写模式打开该文件并返回os.File指针
func TempFile(dir, prefix string) (f *os.File, err error)

5. strings/bytes

为了能像读写文件一样去操作 strings 和 []byte,strings/bytes 提供了对应类型的 io 接口实现。

5.1 strings

strings.Reader 将一个字符串转换成,可读的类文本对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Reader
	func NewReader(s string) *Reader
	func (r *Reader) Len() int
	func (r *Reader) Read(b []byte) (n int, err error)
	func (r *Reader) ReadByte() (b byte, err error)
	func (r *Reader) UnreadByte() error
	func (r *Reader) ReadRune() (ch rune, size int, err error)
	func (r *Reader) UnreadRune() error
	func (r *Reader) Seek(offset int64, whence int) (int64, error)
	func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)
	func (r *Reader) WriteTo(w io.Writer) (n int64, err error)

type Reader struct {
    // 内含隐藏或非导出字段
}

type Replacer
	// 多组old、new字符串对,
	func NewReplacer(oldnew ...string) *Replacer

	// 在传入的 s 中执行替换,用初始化时的 new 替换 old
	func (r *Replacer) Replace(s string) string
	// 对 s 进行替换并写入 w
	func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)

5.2 bytes

bytest.Reader 将一个 []byte 转换成可读的类文本对象。而比较特殊的是 Buffer 对象,这是一个可读写的字节缓冲。

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
type Reader
	func NewReader(b []byte) *Reader
	func (r *Reader) Len() int
	func (r *Reader) Read(b []byte) (n int, err error)
	func (r *Reader) ReadByte() (b byte, err error)
	func (r *Reader) UnreadByte() error
	func (r *Reader) ReadRune() (ch rune, size int, err error)
	func (r *Reader) UnreadRune() error
	func (r *Reader) Seek(offset int64, whence int) (int64, error)
	func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)
	func (r *Reader) WriteTo(w io.Writer) (n int64, err error)

type Reader struct {
    // 内含隐藏或非导出字段
}


// 可读写的字节缓冲
type Buffer struct {
    // 内含隐藏或非导出字段
}

type Buffer
	func NewBuffer(buf []byte) *Buffer
	func NewBufferString(s string) *Buffer
	// Reset重设缓冲,因此会丢弃全部内容,等价于b.Truncate(0)
	func (b *Buffer) Reset()
	func (b *Buffer) Len() int
	// 返回未读取部分字节数据的切片
	func (b *Buffer) Bytes() []byte
	func (b *Buffer) String() string
	func (b *Buffer) Truncate(n int)
	// 必要时会增加缓冲的容量,以保证n字节的剩余空间
	func (b *Buffer) Grow(n int)
	// 返回未读取部分前n字节数据的切片,并且移动读取位置,就像调用了Read方法一样
	// 切片只在下一次调用b的读/写方法前才合法
	func (b *Buffer) Next(n int) []byte

	func (b *Buffer) Read(p []byte) (n int, err error)
	func (b *Buffer) ReadByte() (c byte, err error)
	func (b *Buffer) UnreadByte() error
	func (b *Buffer) ReadRune() (r rune, size int, err error)
	func (b *Buffer) UnreadRune() error

	// ReadBytes读取直到第一次遇到delim字节,返回一个包含已读取的数据和delim字节的切片
	func (b *Buffer) ReadBytes(delim byte) (line []byte, err error)
	func (b *Buffer) ReadString(delim byte) (line string, err error)
	
	// Write将p的内容写入缓冲中,如必要会增加缓冲容量。返回值n为len(p
	func (b *Buffer) Write(p []byte) (n int, err error)
	func (b *Buffer) WriteString(s string) (n int, err error)
	func (b *Buffer) WriteByte(c byte) error
	func (b *Buffer) WriteRune(r rune) (n int, err error)
	func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error)
	func (b *Buffer) WriteTo(w io.Writer) (n int64, err error)

Buffer 的初始化有三种方式:

  1. NewBuffer(buf []byte):
    • 使用buf作为初始内容创建并初始化一个Buffer
    • 用于创建一个用于读取已存在数据的buffer
    • 也用于指定用于写入的内部缓冲的大小,此时,buf应为一个具有指定容量但长度为0的切片
  2. func NewBufferString(s string) *Buffer:
    • 使用s作为初始内容创建一个用于读取已存在数据的buffer
  3. new(Buffer): 大多数情况下使用,初始化一个Buffer

6. bufio

bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口。因为有了缓存 bufio 可以实现下面这些非常使用的功能:

  1. 读取文件直至特定的分隔符
  2. 文件扫描,实现按行或者按照特定分隔符流式读取文件

6.1 文件分割

为了实现读取文件直至xxx功能,bufio 定义了一个特殊的函数类型:

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

SplitFunc类型代表用于对输出作词法分析的分割函数。bufio 中他有如下实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 将每个字节作为一个token返回
func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)

// 将每个utf-8编码的unicode码值作为一个token返回
func ScanRunes(data []byte, atEOF bool) (advance int, token []byte, err error)

// 将单词(空白符分割)作为一个 token 返回
func ScanWords(data []byte, atEOF bool) (advance int, token []byte, err error)

// 将每一行文本去掉末尾的换行标记作为一个token返回
func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error)

6.2 文件扫描

Scanner 使用 SplitFunc 定义的词法分割函数,实现文件扫描:

 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
type Scanner struct {
    // 内含隐藏或非导出字段
}

type Scanner
	// 返回一个从r读取数据的Scanner,默认的分割函数是ScanLines
	func NewScanner(r io.Reader) *Scanner
	// 设置该Scanner的分割函数
	func (s *Scanner) Split(split SplitFunc)

	// Scan 方法获取当前位置的token(该token可以通过Bytes或Text方法获得)
	// 并让Scanner的扫描位置移动到下一个token
	// 当扫描因为抵达输入流结尾或者遇到错误而停止时,本方法会返回false
	// 在Scan方法返回false后,Err方法将返回扫描时遇到的任何错误;除非是io.EOF,此时Err会返回nil
	func (s *Scanner) Scan() bool

	// 返回最近一次Scan调用生成的token
	func (s *Scanner) Bytes() []byte
	func (s *Scanner) Text() string

	// 反回 Scanner 遇到的第一个非 EOF 的错误
	func (s *Scanner) Err() error

scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
    fmt.Println(scanner.Text()) // Println will add back the final '\n'
}
if err := scanner.Err(); err != nil {
    fmt.Fprintln(os.Stderr, "reading standard input:", err)
}

6.3 带缓存 IO

bufio 剩下的部分就是实现了一个带缓存的 io

Reader

 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
34
35
36
37
38
39
40
type Reader struct {
    // 内含隐藏或非导出字段
}

type Reader
	// 创建一个具有默认大小缓冲、从r读取的*Reader
	func NewReader(rd io.Reader) *Reader
	// 创建一个具有最少有size尺寸的缓冲、从r读取的*Reader
	// 如果参数 r 已经是一个具有足够大缓冲的* Reader类型值,会返回r
	func NewReaderSize(rd io.Reader, size int) *Reader

	// 丢弃缓冲中的数据,清除任何错误
	func (b *Reader) Reset(r io.Reader)
	// 返回缓冲中现有的可读取的字节数
	func (b *Reader) Buffered() int

	// Peek返回输入流的下n个字节,而不会移动读取位置
	// 返回的[]byte只在下一次调用读取操作前合法
	// 如果Peek返回的切片长度比n小,它也会返会一个错误说明原因
	// 如果n比缓冲尺寸还大,返回的错误将是ErrBufferFull。
	func (b *Reader) Peek(n int) ([]byte, error)

	// Read读取数据写入p
	// 本方法一次调用最多会调用下层Reader接口一次Read方法,因此返回值n可能小于len(p)
	// 读取到达结尾时,返回值n将为0而err将为io.EOF
	func (b *Reader) Read(p []byte) (n int, err error)
	func (b *Reader) ReadByte() (c byte, err error)
	func (b *Reader) UnreadByte() error
	func (b *Reader) ReadRune() (r rune, size int, err error)
	func (b *Reader) UnreadRune() error
	
	// ReadLine 和 ReadSlice 是比较底层的函数,不建议使用
	// 因为存在写满,或者可能被重写的问题
	func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
	func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
	
	// 读取直到第一次遇到delim字节,返回一个包含已读取的数据和delim字节的切片
	func (b *Reader) ReadBytes(delim byte) (line []byte, err error)
	func (b *Reader) ReadString(delim byte) (line string, err error)
	func (b *Reader) WriteTo(w io.Writer) (n int64, err error)

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
25
26
27
type Writer struct {
    // 内含隐藏或非导出字段
}

type Writer
	// 创建一个具有默认大小缓冲、写入w的*Writer
	func NewWriter(w io.Writer) *Writer
	// 创建一个具有最少有size尺寸的缓冲、写入w的*Writer
	// 如果参数w已经是一个具有足够大缓冲的*Writer类型值,会返回w
	func NewWriterSize(w io.Writer, size int) *Writer
	
	// Reset丢弃缓冲中的数据,清除任何错误,将b重设为将其输出写入w
	func (b *Writer) Reset(w io.Writer)
	// 返回缓冲中已使用的字节数
	func (b *Writer) Buffered() int
	// 返回缓冲中还有多少字节未使用
	func (b *Writer) Available() int

	// 将p的内容写入缓冲。返回写入的字节数。如果返回值nn < len(p),还会返回一个错误说明原因
	func (b *Writer) Write(p []byte) (nn int, err error)
	func (b *Writer) WriteString(s string) (int, error)
	func (b *Writer) WriteByte(c byte) error
	func (b *Writer) WriteRune(r rune) (size int, err error)

	// 将缓冲中的数据写入下层的io.Writer接口
	func (b *Writer) Flush() error
	func (b *Writer) ReadFrom(r io.Reader) (n int64, err error)

ReadWriter

ReadWriter类型保管了指向Reader和Writer类型的指针,因此实现了io.ReadWriter接口。Reader 和 Writer 中的缓存是各自内部创建的,彼此之间不存在共享,ReadWriter 只是做了一个读写分派。

1
2
3
4
5
6
type ReadWriter struct {
    *Reader
    *Writer
}
// 申请创建一个新的、将读写操作分派给r和w 的ReadWriter
func NewReadWriter(r *Reader, w *Writer) *ReadWriter