【终于明白了】源码+图解分析梳理 Golang 中输入输出I/O

前言

输入输出(I/O)是一个程序最基础的一部分。而Golang中涉及io的包非常多,对于小白很容易混淆,现在就来花一点时间来把梳理清楚。

本文将主要涉及以下的几个包

  • io
    • io.ioutil
  • bufio
  • bytes
  • strings
  • net
  • os

标准库的区别

首先需要从高层次上来区分开这几个包。在官网Standard library · std · pkg.go.dev中,这几个包的区别和侧重点如下

标准库描述侧重点
io提供了I/O原语基本接口定义基本的接口
io.ioutil实现了一些I/O实用函数为提供方便,封装了一些实用的I/O函数
bufio实现了缓冲的I/O实现了带缓冲的I/O (例如一些大文件)
bytes实现了操作字节切片的功能主要针对字节切片的,里面实现了bytes.Readerbytes.Buffer
strings实现了简单的函数来操作UTF-8编码的字符串主要针对字符串的,里面实现了string.Reader
net为网络I/O提供了一个可移植的接口,包括TCP/IP、UDP、域名解析和Unix域套接字网络句柄,其中的net.conn实现了Reader,Writer
os提供了一个独立于平台的操作系统功能接口文件句柄,其中的os.File os.Stdio os.Stdout os.Stderr实现了Reader,Writer

1. 提供基本接口的io

io库是GO语言中的核心,其中定义了IO操作中的相关接口。

最基础的接口 io.Readerio.Writer

在io中最基础的就是, 这两个的接口如下:

type Reader interface {
	Read(p []byte) (n int, err error) //接收一个字节切片p,将读取的数据放入到p中,返回读取了的字节个数
}

type Writer interface {
	Write(p []byte) (n int, err error) //接收一个字节切片p,将p中的数据写入到文件或文件中,返回写入了的字节个数
}

这俩个接口定义了最基本的读与写。

辅助接口 io.Seekerio.Closer

在io中,定义了 io.Seekerio.Closer来设置光标和关闭。

type Seeker interface {
	Seek(offset int64, whence int) (int64, error) // 设置相对于whence偏移offset的读或者写的偏移量,返回相对于文件开始的新偏移量和一个错误
}

type Closer interface {
	Close() error  //关闭数据流
}
进阶:偏移量指定io.ReaderAtio.WriterAt

io.ReaderAtio.WriterAt两个接口在io.Readerio.Writer的基础上增加了指定偏移量的操作

type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error) //从基本输入源的偏移量 off 处开始,将 len(p) 个字节读取到 p 中,它返回读取的字节数 n(0 <= n <= len(p))以及任何遇到的错误。
}

type WriterAt interface {
    WriteAt(p []byte, off int64) (n int, err error) //从 p 中将 len(p) 个字节写入到偏移量 off 处的基本数据流中。它返回从 p 中被写入的字节数 n(0 <= n <= len(p))以及任何遇到的引起写入提前停止的错误
}
进阶:来源与去处指定 io.ReaderFromio.WiterTo

io.ReaderFromio.WriterTo两个接口相比于io.Readerio.Writer,指定了各自的输入和输出的对象

type ReaderFrom interface {
    ReadFrom(r Reader) (n int64, err error) //从 r 中读取数据,直到 EOF 或发生错误。其返回值 n 为读取的字节数
}

type WriterTo interface {
    WriteTo(w Writer) (n int64, err error) //将数据写入 w 中,直到没有数据可写或发生错误。其返回值 n 为写入的字节数
}
适配:各种数据类型的读取与写入

io库中也针对byte、Rune,string类型的数据实现了各自的Reader和Writer

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

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

type ByteScanner interface {
    ByteReader                    // 内嵌了 ByteReader 接口
    UnreadByte() error            // 将上一次 ReadByte 的字节还原
}
Rune 读一个字符
type RuneReader interface {
	ReadRune() (r rune, size int, err error) //读一个字符r 返回尺寸size 和错误
}

type RuneScanner interface {
	RuneReader
	UnreadRune() error
}

注意:没有RuneWriter接口,据说是开发者认为几乎不需要RuneWriter接口

String 写入一个字符串
type StringWriter interface {
	WriteString(s string) (n int, err error) //字符串s的内容写到w
}
组合:各式各样的接口搭配

上面的都是一些单一的接口,而有些时候同时需要莫两个或者三个的接口的所有功能,所以在io库中提供了这些“小接口”组合而成的“大接口”。

总结:io库中的所有接口概要图

以上的接口可以归纳为下图

实现:接口的各种实现场景

在不同的标准库场景中,分别实现了io库中的接口:

  • bufio中实现了带缓冲的io

  • os中实现了file,stdin,stdout,stderr的io

  • net中实现了网络io

  • strings中实现了字符串io

  • bytes中实现了字节io

  • compress,crypto …

附:io库中的一些变量与方法

除了接口,io库中还有相对应的一些变量、方法和接口实现(后续讲)。

显示各种错误的变量

io库中的变量主要是一些错误。

  • EOF 没有更多输入可以读取
  • ErrClosePipe 读取或写入一个关闭的通道
  • ErrNoProgress 当许多对Read的调用都没有返回任何数据或错误时,通常是Reader实现失败的标志
  • ErrShortBuffer 缓冲区过短
  • ErrShortWrite 写入时接受的字节数比要求的少
  • ErrUnexpectedEOF 在读取一个固定大小的块或数据结构的过程中遇到了EOF
各种相关操作方法
  • 复制
    • func Copy(dst Writer, src Reader) (written int64, err error) 复制
    • func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) 缓冲复制
    • func CopyN(dst Writer, src Reader, n int64) (written int64, err error) 复制N个字节
  • 管道
    • func Pipe() (*PipeReader, *PipeWriter) 创建一个内存中同步管道
  • 读取
    • func ReadAll(r Reader) ([]byte, error) 读取所有
    • func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) 至少读取min个字节
    • func ReadFull(r Reader, buf []byte) (n int, err error) 读取buf长度
  • 写入
    • func WriteString(w Writer, s string) (n int, err error) 将字符串s的内容写到w,w接受一个字节切片

2. 实用的io.ioutil

可以看到io大部分都只是提供了一些接口,所以使用起来不是那么方便。所以标准库io.ioutil提供了一些方便的操作函数。

函数说明
NopCloser(r io.Reader) io.ReadCloser返回一个包裹起给定 Reader r 的 ReadCloser , 这个 ReadCloser 带有一个无参数的 Close 方法
ReadAll(r io.Reader) ([]byte, error)对 r 进行读取, 直到发生错误或者遇到 EOF 为止, 然后返回被读取的数据
ReadDir(dirname string) ([]os.FileInfo, error)读取 dirname 指定的目录, 并返回一个根据文件名进行排序的目录节点列表
ReadFile(filename string) ([]byte, error)读取名字为 filename 的文件并返回文件中的内容
TempDir(dir, prefix string) (name string, err error)在目录 dir 中新创建一个带有指定前缀 prefix 的临时目录, 然后返回该目录的路径
TempFile(dir, prefix string) (f *os.File, err error)在目录 dir 新创建一个名字带有指定前缀 prefix 的临时文件, 以可读写的方式打开它, 并返回一个 *os.File 指针
WriteFile(filename string, data []byte, perm os.FileMode) error将给定的数据 data 写入到名字为 filename 的文件里面

[实现一] 带缓冲的bufio

疑问:缓冲的作用

很明显,bufioio多了一个缓冲buf。那么,缓冲有何作用?

这是因为在一些很小的写入和读取时,很耗费io。 这时可以把这些小片段的数据放在缓冲中,然后再统一读取或写入,这样可以大大的提高效率。

实现:Reader读取器

bufio中的Reader实现了io中的接口。

bufio.Reader结构体
type Reader struct {
	buf          []byte    // 缓冲区,为字节切片
	rd           io.Reader // reader 由 client 提供
	r, w         int       // 缓冲区读写的位置
	err          error
	lastByte     int // UnreadByte的最后读取的字节;-1表示无效
	lastRuneSize int // UnreadRune最后读取的字符大小;-1表示无效
}

bufio.Reader创建
const (
	defaultBufSize = 4096  //默认大小
)

const minReadBufferSize = 16 //最小尺寸

func NewReaderSize(rd io.Reader, size int) *Reader {
    // Is it already a Reader?
    b, ok := rd.(*Reader)
    if ok && len(b.buf) >= size {
        return b
    }
    if size < minReadBufferSize { //minReadBufferSize==16
        size = minReadBufferSize
    }
    r := new(Reader)
    r.reset(make([]byte, size), rd)
    return r
}

// NewReader returns a new Reader whose buffer has the default size.
func NewReader(rd io.Reader) *Reader {
    return NewReaderSize(rd, defaultBufSize)
}

可以看到有两个方法NewReader()NewReaderSize()可以创建bufio.Reader。区别在于NewReader()使用默认的尺寸4096)来调用NewReaderSize()来实现,而NewReaderSize()则可以自己指定尺寸,注意最小值为16

bufio.Reader的方法及接口实现
方法作用接口实现
Buffered()int返回可从当前缓冲区读取的字节数
Discard(n int) (discarded int, err error)跳过接下来的n个字节,返回被丢弃的字节数
Peek(n int) ([]byte, error)返回下一个n个字节,但是不推进阅读器
Read(p []byte) (n int, err error)读取数据到p中io.Reader
ReadByte() (byte, error)读取并返回一个字节io.ByteReader
ReadBytes(delim byte) ([]byte, error)读到输入中第一次出现delim为止,返回一个包含数据的切片,直到并包括分界符
ReadLine() (line []byte, isPrefix bool, err error)一个低级别的读行原语
ReadRune() (r rune, size int, err error)读取一个UTF-8编码的Unicode字符,并返回符文和它的大小(字节)。
ReadSlice(delim byte) (line []byte, err error)读取直到输入中第一次出现delim,返回一个指向缓冲区中字节的切片
ReadString(delim byte) (string, error)读取直到输入中第一次出现delim,返回一个包含数据的字符串,直到并包括分隔符
Reset(r io.Reader)丢弃任何缓冲数据,重置所有状态,并将缓冲读卡器切换为从r读取
Size() int返回底层缓冲区的大小(字节)
UnreadByte() error解读最后一个字节
UnreadRune() error解读最后一个字符
WriteTo(w io.Writer) (n int64, err error)实现WriteTo接口io.WriterTo

在这里插入图片描述

实现:Writer写入器
bufio.writer结构体
type Writer struct {
	err error      //错误
	buf []byte     //缓冲区
	n   int
	wr  io.Writer
}

bufio.Writer创建
func NewWriterSize(w io.Writer, size int) *Writer {
	// Is it already a Writer?
	b, ok := w.(*Writer)
	if ok && len(b.buf) >= size {
		return b
	}
	if size <= 0 {
		size = defaultBufSize
	}
	return &Writer{
		buf: make([]byte, size),
		wr:  w,
	}
}

func NewWriter(w io.Writer) *Writer {
	return NewWriterSize(w, defaultBufSize)
}

可以看到,类似于bufio.Readerbufio.Writer也有两种方法,一种是默认大小的方式NewWriter和可以指定尺寸的NewWriterSize方式,而当尺寸小于0时为默认大小4096。

bufio.Writer方法及接口实现
方法作用接口实现
Available() int返回缓冲区内有多少个未使用的字节。
Buffered() int返回已经写进当前缓冲区的字节数
Flush() error将任何缓冲数据写入底层的io.Writer
ReadFrom(r io.Reader) (n int64, err error)io.ReaderFrom的实现io.ReaderFrom
Reset(w io.Writer)丢弃任何未刷新的缓冲数据,清除任何错误,并重置b,将其输出写入w
Size() int返回底层缓冲区的大小,单位是字节
Write(p []byte) (nn int, err error)将p的内容写进缓冲区io.Writer
WriteByte(c byte) error写一个字节io.ByteWriter
WriteRune(r rune) (size int, err error)写一个字符
WriteString(s string) (int, error)写一个字符串io.StringWriter

在这里插入图片描述

附:Scanner扫描仪

扫描器模块的主要作用是把数据流分割成一个个标记并除去它们之间的空格

bufio.Scanner结构体
type Scanner struct {
	r            io.Reader // 由client提供的reader.
	split        SplitFunc // 分隔标记的函数.
	maxTokenSize int       // 标记的最大尺寸
	token        []byte    // 由split返回的最后一个token
	buf          []byte    // 用作分割参数的缓冲区
	start        int       // buf中第一个未处理的字节
	end          int       // 缓冲区内的数据结束
	err          error     // 错误
	empties      int       // 连续的空标记的数量
	scanCalled   bool      // 扫描已被调用;缓冲区正在使用中
	done         bool      // 扫描已完成
}

bufio.Scanner创建
const (
	MaxScanTokenSize = 64 * 1024

	startBufSize = 4096 // Size of initial allocation for buffer.
)


func NewScanner(r io.Reader) *Scanner {
	return &Scanner{
		r:            r,                    
		split:        ScanLines,          //默认函数
		maxTokenSize: MaxScanTokenSize,   //64*4096
	}
}
bufio.Scanner的方法
方法作用
Buffer(buf []byte, max int)设置扫描时使用的初始缓冲区和扫描时可能分配的最大缓冲区大小
Bytes() []byte返回调用Scan所产生的最近的token
Err() error返回扫描器遇到的第一个非EOF错误
Scan() bool扫描将扫描器推进到下一个令牌,然后通过字节或文本方法获得令牌
Split(split SplitFunc)设置扫描器的分割功能
Text() string返回由调用Scan产生的最近的令牌,作为一个新分配的字符串,持有其字节数。

[实现二] 文件io的os

与文件进行交互是程序中常见的一个行为。在os库中,实现了对与文件的io。

实现:os.File
os.File结构体
type File struct {
	*file // os specific
}

/*file_unix.go*/
type file struct {
	pfd         poll.FD
	name        string   // 名称
	dirinfo     *dirInfo // nil unless directory being read
	nonblock    bool     // whether we set nonblocking mode
	stdoutOrErr bool     // whether this is stdout or stderr
	appendMode  bool     // whether file is opened for appending
}

可以看到由于不同的操作系统的文件实现方式不一致,所以因操作系统而各异。
在这里插入图片描述

os.File的创建
/*file_unix.go*/ 
// 给定文件描述符fd和文件名 返回一个新的文件
func NewFile(fd uintptr, name string) *File {
	kind := kindNewFile
	if nb, err := unix.IsNonblock(int(fd)); err == nil && nb {
		kind = kindNonBlock
	}
	return newFile(fd, name, kind)
} 
os.File的方法和接口实现
相关函数功能
Create(name string) (*File, error)创建或截断命名的文件
CreateTemp(dir, pattern string) (*File, error)在目录dir中创建一个新的临时文件,打开该文件进行读写,并返回结果文件
Open(name string) (*File, error)打开指定的文件进行阅读
OpenFile(name string, flag int, perm FileMode) (*File, error)通用的打开调用
方法作用接口实现
Chdir() error改变工作目录
Chmod(mode FileMode) error改变模式
Chown(uid, gid int) error改变命名文件的数字UID和GID
Close() error关闭文件
Fd() uintptr返回打开文件的描述符
Name() string返回提交给Open的文件名
Read(b []byte) (n int, err error) | |io.Reader`
ReadAt(b []byte, off int64) (n int, err error)io.ReaderAt
ReadDir(n int) ([]DirEntry, error)读取与文件f相关的目录的内容,并按目录顺序返回DirEntry值的一个片断
ReadFrom(r io.Reader) (n int64, err error)io.ReaderFrom
Readdir(n int) ([]FileInfo, error)读取与文件相关的目录的内容,并返回最多 N 个 FileInfo 值的片断,就像 Lstat 返回的那样,按目录顺序。
Readdirnames(n int) (names []string, err error)读取与文件相关的目录的内容,并按目录顺序返回该目录中最多n个文件名的片断。
Seek(offset int64, whence int) (ret int64, err error)io.Seeker
SetDeadline(t time.Time) error设置一个文件的读写期限
SetReadDeadline(t time.Time) error为未来的读取调用和任何当前被阻止的读取调用设定最后期限
SetWriteDeadline(t time.Time) error为任何未来的写入调用和任何当前被阻断的写入调用设定了截止日期。
Stat() (FileInfo, error)返回描述文件的FileInfo结构
Sync() error将文件的当前内容提交到稳定存储区
SyscallConn() (syscall.RawConn, error)返回一个原始文件
Truncate(size int64) error改变文件的尺寸
Write(b []byte) (n int, err error)io.Writer
WriteAt(b []byte, off int64) (n int, err error)io.WriterAt
WriteString(s string) (n int, err error)io.StringWriter

实现: 其实就是文件的stdin,stdout,stderr

而对于标准输入输出stdin,stdout,stderr它们其实就是指定的File。

var (
	Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
	Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
	Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

[实现三] 网络io的net

实现:Conn接口与conn实现
net.Conn接口

在net库中,定义了底层的接口Conn。

type Conn interface {
	Read(b []byte) (n int, err error)
	Write(b []byte) (n int, err error)
	Close() error
	LocalAddr() Addr
	RemoteAddr() Addr
	SetDeadline(t time.Time) error
	SetReadDeadline(t time.Time) error
	SetWriteDeadline(t time.Time) error
}
net.conn结构体

同时也实现了一个接口类型conn

type conn struct {
	fd *netFD
}
net.conn接口实现
方法接口实现
Read(b []byte) (n int, err error)io.Reader
Write(b []byte) (n int, err error)io.Writer
Close() errorio.Closer

[实现四] 字符串处理的strings

实现:strings.Reader

对字符串进行功效处理,在strings里面有一个Reader数据结构。

string.Reader结构体
type Reader struct {
	s        string  //字符串
	i        int64   // 当前读取下标
	prevRune int     // 先前读取字符的下标
}

string.Reader方法及接口实现
方法作用接口实现
Len() int返回未读部分的字节个数
Read(b []byte) (n int, err error)io.Reader
ReadAt(b []byte, off int64) (n int, err error)io.ReaderA
ReadByte() (byte, error)io.ByteReader
ReadRune() (ch rune, size int, err error)io.RuneReader
Reset(s string)重置
Seek(offset int64, whence int) (int64, error)io.Seeker
Size() int64返回底层字符串的原始长度
UnreadByte() errorio.RuneScanner
WriteTo(w io.Writer) (n int64, err error)io.WriterTo

在这里插入图片描述

[实现五] 字节处理的bytes

实现:bytes.Reader

类似于stringsbyte中也有一个Reader。它两大体类似,只是区别于一个s是string,一个是[]byte。篇幅有限,就不具体展开。

实现:bytes.Buffer

bytes.Buffer是一个可变大小的字节缓冲区,可以读也可以写。

bytes.Buffer结构体
type Buffer struct {
	buf      []byte // 缓冲区 内容为buf[off : len(buf)]
	off      int    //  读在 &buf[off], 写在 &buf[len(buf)]
	lastRead readOp // 上一次读的操作
}

bytes.Buffer方法与接口实现
方法作用接口实现
Bytes() []byte返回一个长度为b.Len()的片断,持有缓冲区的未读部分
Cap() int返回缓冲区基础字节片的容量,即分配给缓冲区数据的总空间
Grow(n int)增加容量
Len() int返回未读部分的字节个数
Next(n int) []byte从缓冲区返回一个包含下一个n个字节的片断,推进缓冲区,就像这些字节是由Read返回的一样
Read(b []byte) (n int, err error)io.Reader
ReadByte() (byte, error)io.ByteReader
ReadFrom(r io.Reader) (n int64, err error)io.ReaderFrom
ReadBytes(delim byte) (line []byte, err error)读取直到delim的字节
ReadRune() (ch rune, size int, err error)io.RuneReader
ReadString(delim byte) (line string, err error)读取直到delim的字符串
Reset(s string)重置
string() string将缓冲区未读部分的内容作为一个字符串返回。
Truncate(n int)丢弃缓冲区中除前n个未读字节外的所有字节,但继续使用相同的分配存储空间。
UnreadByte() errorio.ByteScanner
UnreadRune() errorio.RuneScanner
Write(p []byte) (n int, err error)io.Writer
WriteByte(c byte) errorio.ByteWriter
WriteRune(r rune) (n int, err error)将Unicode代码点r的UTF-8编码追加到缓冲区。
WriteString(s string) (n int, err error)io.StringWriter
WriteTo(w io.Writer) (n int64, err error)io.WriterTo

总结 来个SUMMARY

  1. io是Go中输入输出处理的最基本的库,它定义了许多相关接口
  2. 由于io大部分是定义的接口,使用起来不太实用。io.ioutil提供了许多方便的实用函数
  3. 其它库从不同方面分别实现了io中的接口
    1. bufio实现了带缓冲的io
    2. os实现了文件io
    3. net实现了网络io
    4. strings实现了字符串处理io
    5. bytes实现了字节处理io

在这里插入图片描述

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

HTmonster

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值