bufio是buffered I/O的缩写,这个代码包中的程序实体实现的I/O操作都内置了缓冲区。bufio包中的数据类型主要有:
与io包中的数据类型类似,这些类型的值也都需要在初始化的时候,包装一个或多个简单I/O接口类型的值。(简单接口类型值的就是io包中的那些简单接口。)
bufio.Reader
类型值中的缓冲区的作用bufio.Reader
类型值内的缓冲区,是一个数据存储中间,它介于底层读取器(初始化此类值的时候传入io.Reader
类型的参数值)与读取方法及其调用方之间。
bufio.Reader
值的读取方法一般都会先从其所属值的缓冲区中读取数据,必要的时候,它们还会预先从底层读取器那里读取一部分数据,并暂存于缓冲区中以备后用。
有这样一个缓冲区的好处是,可以在大多数时候降低读取方法的执行时间,虽然读取方法有时还要负责填充缓冲区,但从总体来看,读取方法平均执行时间一般会因此有大幅的缩短。
bufio.Reader
类型并不是开箱即用的,它包含一些需要显式初始化的字段,如下:
// Reader为io.Reader对象实现缓冲。
type Reader struct {
// 字节切片,代表缓冲区,
// 虽然这是切片类型,但是它的长度却是在初始化的时候指定,并且在之后保持不变
buf []byte
rd io.Reader //客户端提供的reader,代表底层读取器,缓冲区中的数据就是从这里拷贝来的
r, w int // buf 读写位置
// r 代表对缓冲区进行下一次读取时的开始索引,称为已读计数
// w 代表对缓冲区进行下一次写入时的开始索引,称为已写计数
// 它的值用于表示在从底层读取器获得数据时发生的错误
// 这里值在被读取或忽略之后,该字段会被设置为nil
err error
// UnreadByte读取的最后一个字节; -1表示无效
// 用于记录缓冲区中最后一个被读取的字节,读回退时会用到它的值
lastByte int
// UnreadRune读取的最后一个rune的大小; -1表示无效
// 用于记录缓冲区中最后一个被读取的Unicode字符所占用的字节数,
// 读回退的时候会用到它的值,这个字段只会在其所属值的ReadRune方法中才会被赋予有意义的值
// 其他情况下,它都被置为-1
lastRuneSize int
}
bufio包提供了两个用于初始化Reader值的函数:
它们都会返回一个*bufio.Reader
类型的值。这里的缓冲区在一个Reader值的生命周期内大小是不变的,所以在有些时候需要做一些权衡。
缓冲区的压缩过程,如下图所示:
实际上,fill方法只要在开始时发现其所属值的已读计数大于0,就会对缓冲区进行一次压缩,之后,如果缓冲区中还有可写的位置,那么该方法就会对其进行填充。
在填充缓冲区的时候,fill方法会试图从底层读取器哪里,读取足够多的字节,并尽量把从已写计数代表的索引位置到缓冲区末尾之间的空间都填满。
在这个过程中fill方法会及时更新已写计数,以保证填充的正确性和顺序性,它还会判断从底层读取器读取数据的时候,是否有错误发生,如果有,那么它就会把错误值赋予给其所属值的err字段,并终止填充流程。
bufio.Writer
类型值中缓冲的数据何时写入底层写入器// Writer为io.Writer对象实现缓冲。
// 如果在写入Writer时发生错误,将不再接受任何数据,
// 并且所有后续写入和Flush都将返回错误。
// 写入所有数据之后,客户端应调用Flush方法以确保所有数据都已转发到底层io.Writer。
type Writer struct {
err error // 它的值用于表示在向底层写入器写数据时发生的错误
buf []byte // 代表缓冲区,在初始化之后,它的长度会保持不变
n int // 代表对缓冲区进行下一次写入时的开始索引,称为写入计数
wr io.Writer // 代表底层写入器
}
bufio.Writer
类型有一个名为Flush的方法,它的主要功能是把相应缓冲区中暂存的所以数据,都写到底层写入器中,数据一旦被写入底层写入器,该方法就会把它们从缓冲区中删除掉。
这里的删除有时候只是逻辑删除。不论是否成功写入了所有暂存数据,Flush方法都会妥当处置,并保证不会出现重写或者漏写的情况。
bufio.Writer
类型拥有的所以数据写入方法都会在必要的时候调用它的Flush方法。
io.ReaderFrom
接口的实现之后,直接调用其ReadFrom
方法把参数值持有的数据写进去。只要缓冲区中的可写空间无法容纳需要写入的新数据,Flush方法就一定会被调用,bufio.Writer
类型的一些方法有时候还会试图走捷径,跨过缓冲区而直接对接数据供需方。
bufio.Reader
类型的读取方法bufio.Reader
类型拥有很多用于读取数据的指针方法,这里有四个方法可以作为不同读取流程的代表:
func (b *Reader) Peek(n int) ([]byte, error)
Peek:读取并返回其缓冲区中n个未读字节,并且它会从已读计数代表的索引位置开始读。
bufio.ErrBufferFull
变量的值作为第二个结果,用来表示虽然缓冲区被压缩和填满了,但是仍然不满足要求nil
Peek方法的一个特点,即使它读取了缓冲区中的数据,也不会改变已读计数的值。其他的读取方法不是这样的。
func (b *Reader) Read(p []byte) (n int, err error)
Read:把缓冲区中的未读字节,依次拷贝到其参数p代表的字节切片中,并立即根据实际拷贝的字节数增加已读计数的值。
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
ReadSlice:持续地读取数据,直到遇到调用方给定的分隔符为止。
先在缓冲区的未读部分中寻找分隔符,如果未找到,并且缓冲区未满,那么调动fill方法对缓冲区进行填充,然后再次寻找,如此往复。
bufio.ErrBufferFull
(缓冲区已满的错误)作为第二个结果func (b *Reader) ReadBytes(delim byte) ([]byte, error)
ReadBytes:持续地读取数据,直到遇到调用方给定的分隔符为止。
Peek、ReadSlice、ReadLine方法都可能会造成内容泄露,在正常情况下,它们都会直接返回基于缓冲区的字节切片,调用方可以通过这些方法的结果值访问到缓冲区的其他部分,甚至修改缓冲区中的内容,这是非常危险的。