Go语言中io.Reader和io.Writer的详解与实现

一、前言

也许对这两个接口和相关的一些接口很熟悉了,但是你脑海里确很难形成一个对io接口的继承关系整天的概貌,原因在于godoc缺省并没有像javadoc一样显示官方库继承关系,这导致了我们对io接口的继承关系记忆不深,在使用的时候还经常需要翻文档加深记忆。

本文试图梳理清楚Go io接口的继承关系,提供一个io接口的全貌。

二、io接口回顾

首先我们回顾一下几个常用的io接口。标准库的实现是将功能细分,每个最小粒度的功能定义成一个接口,然后接口可以组成成更多功能的接口。

最小粒度的接口

type Reader interface {
  Read(p []byte) (n int, err error)
}
type Writer interface {
  Write(p []byte) (n int, err error)
}
type Closer interface {
  Close() error
}
type Seeker interface {
  Seek(offset int64, whence int) (int64, error)
}
type ReaderFrom interface {
  ReadFrom(r Reader) (n int64, err error)
}
type WriterTo interface {
  WriteTo(w Writer) (n int64, err error)
}
type ReaderAt interface {
  ReadAt(p []byte, off int64) (n int, err error)
}
type WriterAt interface {
  WriteAt(p []byte, off int64) (n int, err error)
}

以及

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

ByteScanner比ByteReader多了一个UnreadByte方法。

type ByteScanner interface {
  ByteReader
  UnreadByte() error
}
type RuneReader interface {
  ReadRune() (r rune, size int, err error)
}
type RuneScanner interface {
  RuneReader
  UnreadRune() error
}

组合接口

Go标准库还定义了一些由上面的单一职能的接口组合而成的接口。

type ReadCloser interface {
  Reader
  Closer
}
type ReadSeeker interface {
  Reader
  Seeker
}
type ReadWriter interface {
  Reader
  Writer
}
type ReadWriteCloser interface {
  Reader
  Writer
  Closer
}
type ReadWriteSeeker interface {
  Reader
  Writer
  Seeker
}
type WriteCloser interface {
  Writer
  Closer
}
type WriteSeeker interface {
  Writer
  Seeker
}

从它们的定义上可以看出,它们是最小粒度的组合。

最小接口的扩展

有些结构体struct实现并且扩展了接口,这些结构体是。

type LimitedReader struct {
  R Reader // underlying reader
  N int64 // max bytes remaining
}
type PipeReader struct {
  // contains filtered or unexported fields
}
type PipeWriter struct {
  // contains filtered or unexported fields
}
type SectionReader struct {
  // contains filtered or unexported fields
}

下面我会将它们的继承关系画出来。

一些辅助方法

一些辅助方法可以生成特殊类型的Reader或者Writer:

func LimitReader(r Reader, n int64) Reader
func MultiReader(readers ...Reader) Reader
func TeeReader(r Reader, w Writer) Reader
func MultiWriter(writers ...Writer) Writer

三、继承关系

当然,Go语言中并没有Java中那样的继承关系,而是基于duck type形式实现,我用下图尝试展示Go io接口的继承关系。

其中黄色是 bufio 包下的类型,

绿色是 archive.tar 包下的类型,

蓝色是 bytes 包下的类型,

粉红色是 strings包下的类型,

紫色是 crypto.tls 包下的类型。

Rand是math.rand包下的类型。

File是os包下的内容。

`Rand`左边的那个Reader是image.jpeg下的内容。

我们最常用的是包io、bytes、bufio下的类型,所以这几个包下的类型要记牢,在第三库中经常会出现它们的身影。

上图中并没有把mime/multipart.File和 net/http.File列出来,主要是图太复杂了,它们实现的接口和os.File类似。

当然你可能会问,你怎么整理的它们的继承关系?事实上,你可以通过godoc -analysis=type -http=:6060生成带继承关系的Go doc,并且它还可以将你本地下载的库中的继承关系也显示出来。

四、总结

以上就是关于Go语言中io.Reader和io.Writer的详解与实现的全部内容,希望这篇文章的内容对大家的学习和工作能有所帮助,如果有疑问可以留言交流。

(0)

相关推荐

  • 深入解析Go语言的io.ioutil标准库使用

    今天我们讲解的是golang标准库里边的io/ioutil包–也就是package io/ioutil 1.ioutil.ReadDir(dirname string)这个函数的原型是这样的 func ReadDir(dirname string) ([]os.FileInfo, error) 不难看出输入的是dirname类型是string类型的 譬如"d:/go",然会是一个FileInfo的切片,其中FileInfo的结构是这样的 复制代码 代码如下: type FileInfo

  • GO语言的IO方法实例小结

    type PipeWriter 复制代码 代码如下: type PipeWriter struct {     // contains filtered or unexported fields } (1)func (w *PipeWriter) Close() error关闭管道,关闭时正在进行的Read操作将返回EOF,若管道内仍有未读取的数据,后续仍可正常读取 复制代码 代码如下: import (  "fmt"  "io" ) func main() {  

  • golang将多路复异步io转成阻塞io的方法详解

    前言 本文主要给大家介绍了关于golang 如何将多路复异步io转变成阻塞io的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: package main import ( "net" ) func handleConnection(c net.Conn) { //读写数据 buffer := make([]byte, 1024) c.Read(buffer) c.Write([]byte("Hello from server")) } fu

  • Java语言中flush()函数作用及使用方法详解

    最近在学习io流,发现每次都会出现flush()函数,查了一下其作用,起作用主要如下 //------–flush()的作用--------– 笼统且错误的回答: 缓冲区中的数据保存直到缓冲区满后才写出,也可以使用flush方法将缓冲区中的数据强制写出或使用close()方法关闭流,关闭流之前,缓冲输出流将缓冲区数据一次性写出.flash()和close()都使数据强制写出,所以两种结果是一样的,如果都不写的话,会发现不能成功写出 针对上述回答,给出了精准的回答 FileOutPutStream

  • C语言中access/_access函数的使用实例详解

    在Linux下,access函数的声明在<unistd.h>文件中,声明如下: int access(const char *pathname, int mode); access函数用来判断指定的文件或目录是否存在(F_OK),已存在的文件或目录是否有可读(R_OK).可写(W_OK).可执行(X_OK)权限.F_OK.R_OK.W_OK.X_OK这四种方式通过access函数中的第二个参数mode指定.如果指定的方式有效,则此函数返回0,否则返回-1. 在Windows下没有access函

  • Go语言中strings和strconv包示例代码详解

    前缀和后缀 HasPrefix判断字符串s是否以prefix开头: strings.HaxPrefix(s string, prefix string) bool 示例: package main import ( "fmt" "strings" ) func main() { pre := "Thi" str1 := "This is a Go program!" fmt.Println(strings.HasPrefix(

  • 汇编语言中mov和lea指令的区别详解

    指令(instruction)是一种语句,它在程序汇编编译时变得可执行.汇编器将指令翻译为机器语言字节,并且在运行时由 CPU 加载和执行. 一条指令有四个组成部分: 标号(可选) 指令助记符(必需) 操作数(通常是必需的) 注释(可选) 最近在学习汇编语言,过程中遇到很多问题,对此在以后的随笔会逐渐更新,这次谈谈mov,lea指令的区别   一,关于有没有加上[]的问题 1,对于mov指令来说: 有没有[]对于变量是无所谓的,其结果都是取值 如: num dw 2 mov bx,num mov

  • C语言中scanf与scanf_s函数的使用详解

    目录 1.scanf_s(是vs提供的函数) 2.scanf(标准的库函数) 3.总结 1.scanf_s(是vs提供的函数) a.代码1 int main() { char a = 0; //scanf_s("%c", &a, 1); scanf_s("%c", &a, sizeof(a)); return 0; } scanf_s有三个参数,最后一个是变量a所占据空间的大小(单位为字节),这里可以写1,也可以写sizeof(a).如果a为整型的话

  • Go语言中Slice常见陷阱与避免方法详解

    目录 前言 slice 作为函数 / 方法的参数进行传递的陷阱 slice 通过 make 函数初始化,后续操作不当所造成的陷阱 性能陷阱 内存泄露 扩容 前言 Go 语言提供了很多方便的数据类型,其中包括 slice.然而,由于 slice 的特殊性质,在使用过程中易犯一些错误,如果不注意,可能导致程序出现意外行为.本文将详细介绍 使用 slice 时易犯的一些错误,帮助读者更好的使用 Go 的 slice,避免犯错误. slice 作为函数 / 方法的参数进行传递的陷阱 slice 作为参数

  • R语言中的fivenum与quantile()函数算法详解

    fivenum()函数: 返回五个数据:最小值.下四分位数数.中位数.上四分位数.最大值 对于奇数个数字=5,fivenum()先排序,依次返回最小值.下四分位数.中位数.上四分位数.最大值 > fivenum(c(1,12,40,23,13)) [1] 1 12 13 23 40 对于奇数个数字>5,fivenum()先排序,我们可以求取最小值,最大值,中位数.在排序中,最小值与中位数中间,若为奇数,取其中位数为下四分位数,若为偶数,取最中间两个数的平均值为下四分位数:在排序中,中位数与最大

  • C语言中0数组\柔性数组的使用详解

    前言: 上次看到一篇面试分享,里面有个朋友说,面试官问了char[0] 相关问题,但是自己没有遇到过,就绕过了这个问题. 我自己在这篇文章下面做了一些回复. 现在我想结合我自己的理解,解释一下这个 char[0] C语言柔性数组的问题. 0数组和柔性数组的介绍 0数组顾名思义,就是数组长度定义为0,我们一般知道数组长度定义至少为1才会给它分配实际的空间,而定义了0的数组是没有任何空间,但是如果像上面的结构体一样在最后一个成员定义为零数组,虽然零数组没有分配的空间,但是它可以当作一个偏移量,因为数

  • C语言中typedef的用法以及#define区别详解

    目录 1.简洁定义 2.为已有类型起别名 为字符数组起别名 为指针起别名 3.typedef 和 #define 的区别 总结 1.简洁定义 C语言允许为一个数据类型起一个新的别名,就像给人起"绰号"一样.而编程中起别名,是为了编程人员编程方便,例如: 定义如下结构体 struct stu { int ID; char name[20]; float score[3]; char *data; }; 要想定义一个结构体变量就得这样写: struct stu Marry://Marry是

  • Rust语言中的String和HashMap使用示例详解

    目录 String 新建字符串 更新字符串 使用 + 运算符或 format! 宏拼接字符串 索引字符串 字符串 slice 遍历字符串 HashMap 新建 HashMap HashMap 和 ownership 访问 HashMap 中的值 更新 HashMap 直接覆盖 新插入 更新旧值 总结 String 字符串是比很多开发者所理解的更为复杂的数据结构.加上 UTF-8 的不定长编码等原因,Rust 中的字符串并不如其它语言中那么好理解. Rust 的核心语言中只有一种字符串类型:str

随机推荐