23-io

io包中接口的好处与优势

提高不同程序实体之间的互操作性。

在io包中,有这样几个拷贝数据的函数:io.Copyio.CopyBufferio.CopyN。这几个函数在功能上略有差别,但它们首先会接收两个参数:

  • 用于代表数据目的地,io.Writer类型的参数dst
  • 用于代表数据来源的,io.Reader类型的参数src

这些函数的功能大致上都是把数据从src拷贝到dst。不论给予的参数是什么类型,只要实现了这两个接口,就几乎可以正常执行。函数中还会对必要的参数值进行有效性检查,如果检查不通过,它的执行也是不能够成功结束的

// 创建字符串读取器
src := strings.NewReader(
 "CopyN copies n bytes (or until an error) from src to dst. " +
  "It returns the number of bytes copied and " +
  "the earliest error encountered while copying.")
//  创建字符串构造器
dst := new(strings.Builder)
written, err := io.CopyN(dst, src, 58)
if err != nil {
 fmt.Printf("error: %v\n", err)
} else {
 fmt.Printf("Written(%d): %q\n", written, dst.String())
}

变量src和dst的类型分别是strings.Readerstrings.Builder,当它们被传入到io.CopyN函数时,就已经被包装成了io.Readerio.Writer类型的值。

为了优化的目的,io.CopyN函数中的代码会对参数值进行在包装,也会检测这些参数值是否还实现了别的接口,甚至还会去探求某个参数值被扮装后的实际类型,是否为某个特殊的类型。从总体上来看,这些代码都是面向参数声明中的接口来做的,极大的扩展了它的适用范围和应用场景

换个角度,正因为strings.Readerstrings.Builder类型都实现了不少接口,所以它们的值才能被使用在更广阔的场景中。Go语言的各种库中,能够操作它们的函数和数据类型明显多了很多。这就是strings和bytes包中的数据类型实现了若干个接口之后的最大好处。这是面向接口编程的最大好处

io.ReaderioWriter这两个核心接口是很多接口的扩展对象和设计源泉,很多数据类型实现了io.Reader接口,是因为它们提供了从某处读取数据的功能。

不少类型的设计初衷是:实现这两个核心接口的某个,或某些扩展接口,以提供比单纯的字节序列读取或写入更加丰富的功能。

在Go语言中,对接口的扩展是通过接口类型之间的嵌入来实现的,称为接口组合。Go语言提倡使用小接口加上接口组合的方式,来扩展程序的行为以及增加程序的灵活性。

io.Reader扩展接口和实现类型及其功能

在io包中,io.Reader的扩展接口:

  1. io.ReaderWriter:既是io.Reader的扩展接口,也是io.Writer的扩展接口。该接口定义了一组行为,包含且仅包含了基本的字节序列读取方法Read和字节序列写入方法Write。
  2. io.ReaderCloser:此接口处理包含基本的字节序列读取方法之外,还有基本的关闭方法Close,一般用于关闭数据读写的通路。
  3. io.ReadWriteCloser:三个接口的组合。
  4. io.ReaderSeeker:此接口的特点是拥有一个用于寻找读写位置的基本方法Seek,该方法可以根据规定的偏移量基于数据的起始位置、末尾为止或者当前读写为止寻找到新的读写位置。新的读写为止用于表明下一次读或写的起始索引。Seek是io.Seeker接口唯一拥有的方法。
  5. io.ReadWriteSeeker:是三个接口的组合。

在io包中的io.Reader接口的实现类型:

  1. *io.LimitedReader:此类型的基本类型会包装io.Reader类型的值,并提供一个外的受限读取的功能。

    受限读取是指,此类型的读取方法Read返回的总数据量会收到限制,无论该方法被调用多少次,这个限制由该类型的字段N致命,单位为字节。

  2. *io.SelectionReader:此类型的基本类型可以包装io.ReaderAt类型的值,并且会限制它的Read方法,只能怪读取原始数据中的某一个部分(或者某一段)。

    这个数据段的起始位置和末尾位置,需要在初始化的时候指明,并且之后无法修改。该类型值的行为与切片类似,只会对外暴露其窗口中的那些数据。

  3. *io.teeReader:此类型是包级私有的数据类型,是io.TeeReader函数结果值的实际类型,这个函数接受两个参数r和w,类型分别是io.Readerio.Writer

    其结果值的Read方法会把r中的数据经过作为方法参数的字节切片p写入到w。

  4. io.multiReader:此类型是包级私有的数据类型,类似的,io包中有一个名为MutliReader的函数,它可以接受若干个io.Reader类型的参数值,并返回一个实际类型为io.mutliReader的结果值。

这个结果值的Reader方法被调用时,他会顺序地从前面那些io.Reader类型的参数值中读取数据。称之为多对象读取器。

  1. io.pipe:此类型是包级私有的数据类型,不但实现io.Reader接口,还实现io.Writer接口。

    io.PipeReaderio.PipeWriter类型拥有的所以指针方法都以它为基础,这些方法只是代理io.pipe类型值所拥有的某一个方法而已。因为io.Pipe函数返回这两个乐行的指针值并分别把它们作为其生成的同步内存管道的两端,所以,*io.pipe类型就是io包提供的同步内存管道的核心实现。

  2. io.Pipereader:被视为io.pipe类型的代理类型,它代理了后者的一部分功能,并基于后者实现了io.ReadCloser接口,同时还定义了同步内存管道的读取端。

io包是Go语言标准库中所有I/O相关API的根基,必须对其中的每一个程序实体都了解

io包中的接口以及它们之间的关系

  • 简单接口:没有嵌入其他接口并且只定义了一个方法的接口。

    在io包中,这样的接口一共有11个。分为四大类:读取(5个)、写入(4个)、关闭(1个)、读写位置设定(1个)。目前三种操作属于基本的I/O操作。

  • 核心接口:有这众多的扩展接口和实现类型。

    在io包中,核心接口只有3个:io.Reader(5个扩展接口、6个实现类型)、io.Writerio.Closer

读取

  1. io.ByteReaderio.RuneReader分别定义了读取方法:ReadByte和ReadRune。与io.Reader接口中的Reader方法不同,这两个方法只能读取下一个单一字节和Unicode字符。

    strings.Readerstrings.Buffer都是io.ByteReaderio.RuneReader的实现类型。

    这两个类型还实现了io.ByteScanner接口和io.RuneScanner接口。

    • io.ByteScanner接口内嵌了简单接口io.ByteReader,并定义了额外的UnreadByte方法,它抽象出了可以读取和读回退单个字节的功能集。
    • io.RuneScanner接口内嵌了简单接口io.RuneReader,并定义了额外的UnreadRune方法,它抽象出了可以读取和读回退单个Unicode字符的功能集。
  2. io.ReaderAt接口,其中只定义了一个方法ReadAt,与前面说过的读取方法都不同,ReadAt是一个纯粹的只读方法。只去读取其所属值总包含的字节,而不对这个值进行任何的改动,比如它绝对不能去修改已读计数的值,这是io.ReaderAt接口与其他实现类型之间最重要的一个约定。如果仅仅并发地调用某一个值的ReadAt方法,那么安全性应该是可以得到保障的。

  3. io.WriteTo接口,其中定义了一个WriteTo方法,这是一个读取方法,它会接受一个io.Writer类型的参数值,并会把其所属值中的数据读出并写入到这个参数中。

在io包中,与写入操作有关的接口都与读取操作的相关接口有着一定的对应关系

写入

  1. io.ReaderFrom接口,其中定义了ReadFrom方法,这是一个写入方法,该方法会接受一个io.Reader类型的参数值,并会从该类型值中读出数据,并写入到其所属值中。

  2. io.Writer核心接口,基于它扩展接口除了io.ReadWriterio.ReadWriteCloserio.ReadWriteSeekerio.WriteCloserio.WriteSeeker

读写位置

io.Seeker接口作为读写位置设定相关的接口,定义了一个方法Seek。

数据类型

*os.File,这个类型不但是io.WriterAt接口的实现类型,还实现了io.ByteWritCloserio.ReadWriteSeeker,该类型支持的I/O操作非常丰富。

总结

io包中的接口体系

images

上次修改: 25 November 2019