learn-tech/专栏/Go语言核心36讲/41io包中的接口和工具(下).md
2024-10-16 00:01:16 +08:00

109 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
41 io包中的接口和工具 (下)
上一篇文章中我主要讲到了io.Reader的扩展接口和实现类型。当然io代码包中的核心接口不止io.Reader一个。
我们基于它引出的一条主线只是io包类型体系中的一部分。我们很有必要再从另一个角度去探索一下以求对io包有更加全面的了解。
下面的一个问题就与此有关。
知识扩展
问题io包中的接口都有哪些它们之间都有着怎样的关系
我们可以把没有嵌入其他接口并且只定义了一个方法的接口叫做简单接口。在io包中这样的接口一共有11个。
在它们之中有的接口有着众多的扩展接口和实现类型我们可以称之为核心接口。io包中的核心接口只有3个它们是io.Reader、io.Writer和io.Closer。
我们还可以把io包中的简单接口分为四大类。这四大类接口分别针对于四种操作读取、写入、关闭和读写位置设定。前三种操作属于基本的I/O操作。
关于读取操作我们在前面已经重点讨论过核心接口io.Reader。它在io包中有5个扩展接口并有6个实现类型。除了它这个包中针对读取操作的接口还有不少。我们下面就来梳理一下。
首先来看io.ByteReader和io.RuneReader这两个简单接口。它们分别定义了一个读取方法ReadByte和ReadRune。
但与io.Reader接口中Read方法不同的是这两个读取方法分别只能够读取下一个单一的字节和Unicode字符。
我们之前讲过的数据类型strings.Reader和bytes.Buffer都是io.ByteReader和io.RuneReader的实现类型。
不仅如此这两个类型还都实现了io.ByteScanner接口和io.RuneScanner接口。
io.ByteScanner接口内嵌了简单接口io.ByteReader并定义了额外的UnreadByte方法。如此一来它就抽象出了一个能够读取和读回退单个字节的功能集。
与之类似io.RuneScanner内嵌了简单接口io.RuneReader并定义了额外的UnreadRune方法。它抽象的是可以读取和读回退单个Unicode字符的功能集。
再来看io.ReaderAt接口。它也是一个简单接口其中只定义了一个方法ReadAt。与我们在前面说过的读取方法都不同ReadAt是一个纯粹的只读方法。
它只去读取其所属值中包含的字节而不对这个值进行任何的改动比如它绝对不能去修改已读计数的值。这也是io.ReaderAt接口与其实现类型之间最重要的一个约定。
因此如果仅仅并发地调用某一个值的ReadAt方法那么安全性应该是可以得到保障的。
另外还有一个读取操作相关的接口我们没有介绍过它就是io.WriterTo。这个接口定义了一个名为WriteTo的方法。
千万不要被它的名字迷惑这个WriteTo方法其实是一个读取方法。它会接受一个io.Writer类型的参数值并会把其所属值中的数据读出并写入到这个参数值中。
与之相对应的是io.ReaderFrom接口。它定义了一个名叫ReadFrom的写入方法。该方法会接受一个io.Reader类型的参数值并会从该参数值中读出数据,并写入到其所属值中。
值得一提的是我们在前面用到过的io.CopyN函数在复制数据的时候会先检测其参数src的值是否实现了io.WriterTo接口。如果是那么它就直接利用该值的WriteTo方法把其中的数据拷贝给参数dst代表的值。
类似的这个函数还会检测dst的值是否实现了io.ReaderFrom接口。如果是那么它就会利用这个值的ReadFrom方法直接从src那里把数据拷贝进该值。
实际上对于io.Copy函数和io.CopyBuffer函数来说也是如此因为它们在内部做数据复制的时候用的都是同一套代码。
你也看到了io.ReaderFrom接口与io.WriterTo接口对应得很规整。实际上在io包中与写入操作有关的接口都与读取操作的相关接口有着一定的对应关系。下面我们就来说说写入操作相关的接口。
首先当然是核心接口io.Writer。基于它的扩展接口除了有我们已知的io.ReadWriter、io.ReadWriteCloser和io.ReadWriteSeeker之外还有io.WriteCloser和io.WriteSeeker。
我们之前提及的*io.pipe就是io.ReadWriter接口的实现类型。然而在io包中并没有io.ReadWriteCloser接口的实现它的实现类型主要集中在net包中。
除此之外写入操作相关的简单接口还有io.ByteWriter和io.WriterAt。可惜io包中也没有它们的实现类型。不过有一个数据类型值得在这里提一句那就是*os.File。
这个类型不但是io.WriterAt接口的实现类型还同时实现了io.ReadWriteCloser接口和io.ReadWriteSeeker接口。也就是说该类型支持的I/O操作非常的丰富。
io.Seeker接口作为一个读写位置设定相关的简单接口也仅仅定义了一个方法名叫Seek。
我在讲strings.Reader类型的时候还专门说过这个Seek方法当时还给出了一个与已读计数估算有关的例子。该方法主要用于寻找并设定下一次读取或写入时的起始索引位置。
io包中有几个基于io.Seeker的扩展接口包括前面讲过的io.ReadSeeker和io.ReadWriteSeeker以及还未曾提过的io.WriteSeeker。io.WriteSeeker是基于io.Writer和io.Seeker的扩展接口。
我们之前多次提到的两个指针类型strings.Reader和io.SectionReader都实现了io.Seeker接口。顺便说一句这两个类型也都是io.ReaderAt接口的实现类型。
最后关闭操作相关的接口io.Closer非常通用它的扩展接口和实现类型都不少。我们单从名称上就能够一眼看出io包中的哪些接口是它的扩展接口。至于它的实现类型io包中只有io.PipeReader和io.PipeWriter。
总结
我们来总结一下这两篇的内容。在Go语言中对接口的扩展是通过接口类型之间的嵌入来实现的这也常被叫做接口的组合。而io代码包恰恰就可以作为接口扩展的一个标杆它可以成为我们运用这种技巧时的一个参考标准。
在本文中我根据接口定义的方法的数量以及是否有接口嵌入把io包中的接口分为了简单接口和扩展接口。
同时,我又根据这些简单接口的扩展接口和实现类型的数量级,把它们分为了核心接口和非核心接口。
在io包中称得上核心接口的简单接口只有3个io.Reader、io.Writer和io.Closer。这些核心接口在Go语言标准库中的实现类型都在200个以上。
另外根据针对的I/O操作的不同我还把简单接口分为了四大类。这四大类接口针对的操作分别是读取、写入、关闭和读写位置设定。
其中前三种操作属于基本的I/O操作。基于此我带你梳理了每个类别的简单接口并讲解了它们在io包中的扩展接口以及具有代表性的实现类型。
io包中的接口体系
除此之外我还从多个维度为你描述了一些重要程序实体的功用和机理比如数据段读取器io.SectionReader、作为同步内存管道核心实现的io.pipe类型以及用于数据拷贝的io.CopyN函数等等。
我如此详尽且多角度的阐释正是为了让你能够记牢io代码包中有着网状关系的接口和数据类型。我希望这个目的已经达到了最起码本文可以作为你深刻记忆它们的开始。
最后再强调一下io包中的简单接口共有11个。其中读取操作相关的接口有5个写入操作相关的接口有4个而与关闭操作有关的接口只有1个另外还有一个读写位置设定相关的接口。
此外io包还包含了9个基于这些简单接口的扩展接口。你需要在今后思考和实践的是你在什么时候应该编写哪些数据类型实现io包中的哪些接口并以此得到最大的好处。
思考题
今天的思考题是io包中的同步内存管道的运作机制是什么
戳此查看Go语言专栏文章配套详细代码。