前言

IO 操作是我们在编程中不可避免会遇到的,例如读写文件,Go语言的 io 包中提供了相关的接口,定义了相应的规范,不同的数据类型可以根据规范去实现相应的方法,提供更加丰富的功能。

Go 语言提倡小接口 + 接口组合的方式,来扩展程序的行为以及增加程序的灵活性。io代码包恰恰就可以作为这样的一个标杆,它可以成为我们运用这种技巧时的一个参考标准。

上一篇文章,我们学习了io包中的四个核心接口,本篇文章我们来学习下多个基本接口!

ReaderFrom

io.ReaderFrom 接口定义了 ReadFrom 方法,用于从一个 Reader 中读取数据

  • 入参:Reader r
  • 返回值:n为读取的字节数,err 为读取过程中遇到的error
1
2
3
type ReaderFrom interface {
	ReadFrom(r Reader) (n int64, err error)
}

方法功能详解

  1. 该方法从 Reader r 中读取数据,直到读到文件末尾(EOF)或者 遇到 其他 error
  2. 返回的 n 就是读取的字节数

WriterTo

io.WriteTo 接口定义了 WriteTo 方法,用于将数据写入到一个 Writer 中

  • 入参:Writer w
  • 返回值:n为写入的字节数,err 为写入过程中遇到的 error
1
2
3
type WriterTo interface {
   WriteTo(w Writer) (n int64, err error)
}

方法功能详解

  1. 该方法将数据写入到 w 中,直到没有数据可写或者遇到error
  2. 返回的 n 就是写入的字节数

ReaderAt

io.ReaderAt 接口定义了 ReadAt 方法,用于从指定位置读取数据

  • 入参:读取的起始位置 off,读取后写入字节数组 p
  • 返回值:n 为成功读取的字节数,err为读取过程中遇到的 error
1
2
3
type ReaderAt interface {
	ReadAt(p []byte, off int64) (n int, err error)
}

方法功能详解

  1. 方法从指定的偏移量位置 off 开始读取指定的数据,然后至多读取 len(p) 个字节,写入字节数组 p
  2. 返回成功读取的字节量 n,以及遇到的 error
  3. 当该方法返回的 n < len(p) 时,即读取的数据没有将字节数组p填充满,返回的 err != nil,在这一点上,ReadAt方法要比 Read方法严格(Read方法规定遇到n<len(p),本次调用可以返回 err=nil,然后在下一次调用时返回err!=nil )
  4. 即使方法返回的 n < len(p),也可能会占用整个字节数组 p 作为暂存空间
  5. 如果数据可用,但是不够 len(p)个字节,ReadAt 方法会阻塞,直到所有数据可用 或者 遇到 error,这一点也与 Read 方法不同
  6. 如果方法返回的 n=len(p),并且此时正好读取到了文件末尾,ReadAt 可以返回 err = EOF 或者 err = nil。如果返回的 err 为 nil的话,下次就一定返回 EOF 了。

不能影响seek offset

  1. 如果 ReadAt 方法的入参 off 是由 seek 方法计算得到的,ReadAt 方法不能修改 off 的值

可并行

  1. 对于同一个数据源,可以并行调用 ReadAt 方法读取数据

方法实现需注意

  1. 实现该方法后,一定不要持有字节数组p(不能保存 p 的地址,用于其他地方)

WriteAt

io.WriterAt 接口定义了 WriteAt 方法,将字节数组 p 中的数据,从文件 off 偏移量开始写入到文件中

  • 入参:字节数组p,文件开始写处的偏移量 off
  • 返回值:成功写入的字节数 n,写入过程中遇到的error err
1
2
3
type WriterAt interface {
	WriteAt(p []byte, off int64) (n int, err error)
}

方法功能详解

  1. 方法将 字节数组 p 中的 len(p) 个字节,从文件 off 位置开始,写入到文件中。
  2. 方法返回成功写入的字节个数n (0 <= n <= len(p)) ,以及是否遇到 error
  3. 如果 n < len(p),方法必须返回 err!=nil

不能影响offset

  1. 如果 WriteAt 方法的入参 off 是由 seek 方法计算得到的,ReadAt 方法不能修改 off 的值

可并行

  1. 对于同一个数据源,如果写入的范围不重叠,可以并行调用 WriteAt 方法写数据

方法实现需注意

  1. 实现该方法后,一定不要持有字节数组p(不能保存 p 的地址,用于其他地方)

ByteReader

io.ByteReader 接口定义了 ReadByte 方法,用于读取一个字节

  • 返回值:读取的一个字节,以及可能遇到的 error
1
2
3
type ByteReader interface {
   ReadByte() (byte, error)
}

方法功能详解

  1. 方法读取底层数据流的下一个字节,然后返回该字节,同时返回对应的error
  2. 如果方法读取过程中遇到error,不会消费底层数据的一个字节,相当于当前读取的位置不会变

ByteScanner

ByteScanner 是 ByteReader 接口 和 UnreadByte 方法的组合,因此该组合接口包含了两个方法,一个 ReadByte,一个 UnreadByte。从名字我们也可以看出来,ReadByte 是读取一个字节,UnreadByte 是回退一个字节。

1
2
3
4
type ByteScanner interface {
   ByteReader
   UnreadByte() error
}

方法功能详解

  1. 在调用一次 ReadByte 之后,如果调用一次 UnreadByte,那么下次再调用 ReadByte 方法得到的值,和第一次调用 ReadByte 应该一样,也就是回退了一个位置。
  2. 如果连续两次调用 UnreadByte,中间没有调用 ReadByte,可能会产生 error (这里说的可能,比如从文件开始读取一次,然后两次回退,那么回退到的位置可能就不合法了,具体还是依赖实现方法定义)

ByteWriter

io.ByteWriter 接口定义了 WriteByte 方法,用于向底层文件写入一个字节,然后返回写入过程产生的 error

1
2
3
type ByteWriter interface {
	WriteByte(c byte) error
}

RuneReader

io.RuneReader 接口定义了 ReadRune 方法,读取一个 UTF-8 字符。UTF-8 是一种变长编码规则,从 1 到 4 个字节不等,比如一个字母占一个字节,中文每个字符占用 3 个字节。ReadRune 每次读出的是一个完整的编码字符,比如读汉字,每次就会读出一个汉字。

1
2
3
type RuneReader interface {
	ReadRune() (r rune, size int, err error)
}

RuneScanner

类似 ByteScanner,RuneScanner 也是个组合接口,提供了 UnreadRune 方法,可以回退一个 rune。如果连续两次调用 UnreadRune 方法,可能会产生error。

1
2
3
4
type RuneScanner interface {
	RuneReader
	UnreadRune() error
}

StringWriter

io.StringWriter 接口定义了 WriteString 方法,用于将字符串 s 写入底层的数据流中,返回写入成功的字节数 n,以及可能产生的 error

1
2
3
4
// StringWriter is the interface that wraps the WriteString method.
type StringWriter interface {
   WriteString(s string) (n int, err error)
}

总结

本篇文章介绍了 io包 中的十个基本接口及对应的方法:

  • ReaderFrom: 定义了 ReadFrom 方法,从 Reader 中读取数据
  • WriterTo: 定义了 WriteTo 方法,向 Writer 中写入数据
  • ReaderAt: 定义了 ReadAt 方法,从指定位置读取数据
  • WriterAt: 定义了 WriteAt 方法,从指定位置写入数据
  • ByteReader: 定义了 ReadByte 方法,读取一个字节
  • ByteScanner: 组合接口,除了ReadByte 方法,定义了 UnreadByte 方法,用于回退一个字节
  • ByteWriter: 定义了 WriteByte 方法,向文件写入一个字节
  • RuneReader: 定义了 ReadRune 方法,读取一个 UTF-8 字符
  • RuneScanner: 组合接口,除了 ReadRune 方法,定义了 UnreadRune 方法,用于回退一个 rune
  • StringWriter: 定义了 WriteString 方法,用于向文件写入一个字符串

更多

微信公众号:CodePlayer