(转)Go中常用I/O说明

原文:https://zhuanlan.zhihu.com/p/525137132

1 介绍

输入输出(I/O)是一个程序最基础的部分,Golang中涉及io的包非常多,相互之间的关系也比较复杂,本文尝试对这些I/O之间的关系进行一个系统的梳理,后续将对每一部分开展分析。

2 主要的包

在Golang的标准库中,涉及I/O操作的包主要由如下几种:

  • io,提供了I/O原语的基本接口,这个包重点在于进行接口的定义;
  • io.ioutil,在io的基础上提供了一些使用的I/O函数,但是当前大部分已经被其它的包实现了,后续基本不建议继续使用;
  • bytes,为了方法进行bytes的读写操作,在bytes中定义了Reader,Buffer类型,前者负责从一个bytes中读取各种类型(byte,rune,string)的数据,后者负责向bytes中写入数据;
  • strings,为了方便进行strings的读写操作,在strings中定义了Reader,Builder类型,前者负责从strings中读取各种类型的数据,后者负责向string中写入数据,从而创建一个string;
  • bufio,在io的基础上对Reader和Writer提供一层封装,能够实现具有缓存的读写,目的是为了提高效率;
  • os,提供了File类型,实现了相关的读写操作;
  • net,提供了Conn类型,实现了相关的读写操作。

3 I/O接口定义

首先我们详细介绍一下在io包中定义的一些基本I/O接口,如下图所示

3.1 基本接口

在io中最基础的就是读和写, 分别对应io.Reader与io.Writer:

type Reader interface {
	Read(p []byte) (n int, err error) 
}

type Writer interface {
	Write(p []byte) (n int, err error)
}
  • Reader接口定义了Read方法,接收一个字节切片p,将读取的数据放入到p中,返回读取了的字节个数。
  • Writer接口定义了Write方法,接收一个字节切片p,将p中的数据写入目标地址中(可能是内存,文件或者网络),返回写入了的字节个数。

3.3 Seeker

io.Seeker定义了读写光标的移动操作

type Seeker interface {
	Seek(offset int64, whence int) (int64, error) 
}

3.4 Closer

io.Closer定义了一个读写对象(数据流)的关闭

type Closer interface {
	Close() error  //关闭数据流
}

3.5 组合接口

Reader,Writer,Seeker和Closer的接口自由组合形成一组相关的接口。

3.6 偏移操作接口

io.ReaderAt和io.WriterAt两个接口定义了在特定位置进行读写的操作。

type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error) 
}

type WriterAt interface {
    WriteAt(p []byte, off int64) (n int, err error) 
}
  • ReaderAt接口定义了ReadAt,从基本输入源的偏移量 off 处开始,将 len(p) 个字节读取到 p 中,它返回读取的字节数 n(0 <= n <= len(p))以及任何遇到的错误;
  • WriterAt接口定义了从 p 中将 len(p) 个字节写入到偏移量 off 处的基本数据流中。它返回从 p 中被写入的字节数 n(0 <= n <= len(p))以及任何遇到的引起写入提前停止的错误。

3.7 byte操作接口

针对byte类型定义了一组读写byte的接口。

type ByteReader interface {
    ReadByte() (c byte, err error) //读一个字节
}

type ByteWriter interface {
    WriteByte(c byte) error       //写一个字节
}

type ByteScanner interface {
    ByteReader                    // 内嵌了 ByteReader 接口
    UnreadByte() error            // 将上一次 ReadByte 的字节还原
}
  • io.ByteReader定义了一个读字节的方法;
  • io.ByteWriter定义了一个写字节的方法;
  • io.ByteScanner在ByteReader的基础上增加了一个回退的操作。

3.8 rune操作接口

针对byte类型定义了一组读的接口,没有定义写接,标准库的开发者认为几乎不需要该接口,但是有些类型后面确实又实现了该方法。

type RuneReader interface {
	ReadRune() (r rune, size int, err error) //读一个字符r 返回尺寸size 和错误
}

type RuneScanner interface {
	RuneReader
	UnreadRune() error
}
  • io.RuneReader定义了一个读字符的方法;
  • io.RuneScanner在RuneReader的基础上增加了一个回退的操作。

3.9 string操作接口

StringWriter实现了一个写字符串的操作,实际上用一个Reader操作应该能够完全实现读字符串的操作。

type StringWriter interface {
	WriteString(s string) (n int, err error)
}

3 I/O工具定义

在接口的基础上,io包定义了一组方法,完成对Reader和Writer的进一步封装,方便使用。

3.1 函数

(1)读操作

  • func ReadAll(r Reader) ([]byte, error), 从Reader中读取所有数据;
  • func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) ,从Reader中至少读取min个字节;
  • func ReadFull(r Reader, buf []byte) (n int, err error) ,从Reader中读取数据,并尽量将buf填充满;

(2)写操作

  • func WriteString(w Writer, s string) (n int, err error) ,将字符串s的内容写到w,w接受一个字节切片

(3)复制操作

  • func Copy(dst Writer, src Reader) (written int64, err error) ,将Reader中的数据复制到Writer中;
  • func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) ,通过缓冲,提高复制的效率;
  • func CopyN(dst Writer, src Reader, n int64) (written int64, err error) ,将Reader中的N个字节数据复制到Writer中。

3.1 LimiteReader类型

最多只能返回 N 字节数据,该类型实现了Read方法,即实现了io.Reader接口。

type LimitedReader struct {
    R Reader // underlying reader
    N int64  // max bytes remaining
}

该类型提供了一个工厂函数LimitReader

func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }

使用示例如下:

content := "This Is LimitReader Example"
reader := strings.NewReader(content)
limitReader := &io.LimitedReader{R: reader, N: 8}
for limitReader.N > 0 {
    tmp := make([]byte, 2)
    limitReader.Read(tmp)
    fmt.Printf("%s", tmp)
}

3.2 SectionReader类型

SectionReader的目的是仅读取Reader的某一部分的数据,由于不是顺序读取因此需要ReadAt接口的支持,接口定义如下,内嵌了 ReaderAt 接口,实现了 Read, Seek 和 ReadAt,内部的读取操作全部转化成了ReadAt的操作。:

type SectionReader struct {
    r     ReaderAt     // 该类型最终的 Read/ReadAt 最终都是通过 r 的 ReadAt 实现
    base  int64        // NewSectionReader 会将 base 设置为 off
    off   int64        // 从 r 中的 off 偏移处开始读取数据
    limit int64        // limit - off = SectionReader 流的长度
}

该类型提供了一个工厂函数NewSectionReader

func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader

使用示例如下:

r := strings.NewReader("some io.Reader stream to be read\n")
s := io.NewSectionReader(r, 5, 17)

buf := make([]byte, 9)
if _, err := s.Read(buf); err != nil {
	log.Fatal(err)
}

fmt.Printf("%s\n", buf)

3.3 TeeReader函数

TeeReader 函数返回一个 Reader,在内部是一个teeReader类型,它将从 r 中读到的数据写入 w 中。所有经由它处理的从 r 的读取都匹配于对应的对 w 的写入。它没有内部缓存,即写入必须在读取完成前完成。任何在写入时遇到的错误都将作为读取错误返回。也就是说,我们通过 Reader 读取内容后,会自动写入到 Writer 中去。由于这个函数的返回不需要暴露除Reader以外的任何函数和变量因此底层的数据的结构teeReader没有对外暴露。

func TeeReader(r Reader, w Writer) Reader {
	return &teeReader{r, w}
}

type teeReader struct {
	r Reader
	w Writer
}

3.4 MultiReader函数

MultiReader返回一个Reader接口的工厂方法,能够完成对多个Reader的读操作。

func MultiReader(readers ...Reader) Reader

type multiWriter struct {
	writers []Writer
}

使用例子如下:

r1 := strings.NewReader("first reader ")
r2 := strings.NewReader("second reader ")
r3 := strings.NewReader("third reader\n")
r := io.MultiReader(r1, r2, r3)
if _, err := io.Copy(os.Stdout, r); err != nil {
	log.Fatal(err)
}

3.5 MultiWriter函数

MultiWriter返回一个Writer接口的工厂方法,能够完成对多个Writer的写操作,multiWriter除了实现Writer接口外,还实现了WriterString接口。

func MultiWriter(writers ...Writer) Writer 

type multiWriter struct 

使用例子如下:

r := strings.NewReader("some io.Reader stream to be read\n")

var buf1, buf2 bytes.Buffer
w := io.MultiWriter(&buf1, &buf2)

if _, err := io.Copy(w, r); err != nil {
	log.Fatal(err)
}

fmt.Print(buf1.String())
fmt.Print(buf2.String())

3.6 Discard变量

io包对外提供了一个Discard,这个Discard是一个Writer,但是内部实际上将数据丢弃了。这个工具以一个变量的方式对外暴露,没有以函数或者类型的方式进行暴露,对外提供了Write,WriteString和ReadFrom三个方法。

var Discard Writer = discard{}
type discard struct{}

3.7 NopCloser函数

就是将一个不带Close的Reader封装成ReadCloser,对外以一个工厂函数的方式暴露接口。

func NopCloser(r Reader) ReadCloser {
	return nopCloser{r}
}

type nopCloser struct {
	Reader
}

3.8 Pipe

TODO

4 bytes和strings中的I/O相关类型

bytes和strings分别定了一个字节切片和字符串的常用操作,但是可以将字节切片和字符串看成数据的容器,通过提供一些I/O操作的可以方便内部数据的解析、浏览以及创建。

strings提供了strings.Reader和strings.Builder两个类型,其中Reader主要实现一个字符串的遍历读取,Builder能够更高效的实现一个复杂字符串的拼接,实现的接口如下图。

bytes类似的也提供了类似的两个类型,bytes.Reader和bytes.Buffer,其中Reader主要实现一个字节的遍历读取和strings.Reader类似,Buffer能够则实现了字节切片的读写操作,实现了除io.Closer的其它所有接口。

5 bufio

bufio实际上是标准库提供的一层带有缓存的读写器,能够把实现了io.Reader和io.Writer接口的类型进行一次封装,通过内部的缓存提高读写的效率,通过下图能够看出bufio提供了一个Reader和一个Writer实现了所有的基本I/O接口。

5.1 Reader

// Buffered input.

// Reader implements buffering for an io.Reader object.
type Reader struct {
	buf          []byte
	rd           io.Reader // reader provided by the client
	r, w         int       // buf read and write positions
	err          error
	lastByte     int // last byte read for UnreadByte; -1 means invalid
	lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}

func NewReader(rd io.Reader) *Reader

能够看出只要是一个实现了io.Reader接口的对象就可以被封装成一个bufio.Reader,从而提供带有缓存的读操作。

5.2 Writer

type Writer struct {
	err error
	buf []byte
	n   int
	wr  io.Writer
}

func NewWriter(w io.Writer) *Writer

同样能够看出只要是一个实现了io.Writer接口的对象就可以被封装成一个bufio.Writer,从而提供带有缓存的写操作。

6 fmt包

fmt包实现了类似C语言printf和scanf的格式化I/O。主要分为向外输出内容和获取输入内容两大部分。实际上在内部是使用io.Reader和io.Writer完成相应的I/O操作。

6.1 Print类函数

Print类函数分成三类

  1. 将结果输出到系统的标准输出,os.Stdout,实际上也是一个io.Writer;
  2. 将结果输出到一个字符串,以S开头;
  3. 将结果输出到一个文件中,实际上是一个io.Writer中,以F开头。

另外每一类的Print函数有三种类型:

  1. func Print(a ...interface{}) (n int, err error),直接输出每一个参数,没有格式描述,不进行换行;
  2. func Println(a ...interface{}) (n int, err error),直接输出每一个参数,没有格式描述,不进行换行;
  3. func Printf(format string, a ...interface{}) (n int, err error),有格式说明参数。

6.2 Scan类函数

Scan类函数分成三类

  1. 从系统的标准输入扫描数据,os.Stdio,实际上也是一个io.Reader;
  2. 从字符串扫描数据,以S开头,会将一个string封装成一个io.Reader;
  3. 从文件扫描数据,实际上是从io.Reader扫描数据,以F开头。

另外每一类的Scan函数有三种类型:

  1. func Scan(a ...interface{}) (n int, err error),直接扫描,换行当成空格处理;
  2. func Scanln(a ...interface{}) (n int, err error),直接扫描,碰到换行结束;
  3. func Scanf(format string, a ...interface{}) (n int, err error),带有格式的扫描。

7 其它I/O操作

上面描述了基础的各类IO接口,IO工具以及面向byte、string的基础IO操作。在此基础上有很多包也实现了相应的IO操作,包括os.File,net.Conn等,本小节对这些内容进行详细描述。

6.1 os.File

TODO

6.2 net.Conn

TODO

posted @   liujiacai  阅读(127)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· [翻译] 为什么 Tracebit 用 C# 开发
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· 2分钟学会 DeepSeek API,竟然比官方更好用!
· .NET 使用 DeepSeek R1 开发智能 AI 客户端
· 刚刚!百度搜索“换脑”引爆AI圈,正式接入DeepSeek R1满血版
历史上的今天:
2021-03-08 (转)postgreSQL 12 源码安装
2019-03-08 (转)websphere线程池 连接池设置
点击右上角即可分享
微信分享提示