Golang — bufio Reader详解

bufio 是对 gosdk 种 io.Readerio.writer 的二次打包,以 []byte 作为其 buffer,在 io.Readerio.Writer 的基础上提供了更多方便有效的 io 读取和写入方法。

PS:由于代码过多,对源码函数流程的解释直接编写在源码注释上

io.Reader

io.Readerio.writer 是 gosdk io 包中的非常重要的两个接口,它们内部各包含一个方法 ReadWrite,传入参数 []byte ,返回读取/写入的字节数和错误。

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

io.Reader核心作用为连续读取字节填充传入的 slice,并在读取停止时返回字节数和遇到的错误(无错误时返回nil)。

bufio Reader

bufio.Reader

// Reader implements buffering for an io.Reader object.
type Reader struct {buf          []byterd           io.Reader // reader provided by the clientr, w         int       // buf read and write positionserr          errorlastByte     int // last byte read for UnreadByte; -1 means invalidlastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}

该结构体是 bufio 种所有读取相关方法的 receiver,即所有读取操作都需要在创建一个此结构体类型的对象后才能使用。bufio.Reader 内部包含:

  • buf:读取的字节 buffer
  • rd:客户传入的 io.Reader 对象,即一切读取的字节都来自于该对象
  • r,w:读取和写入的标志位
  • err:当前发生的错误
  • lastByte:在 UnreadByte 方法中会使用到的上一个字节位置变量
  • lastRuneSize:在 UnreadRune 方法中会使用到的上一个 rune 位置变量

构造函数

构造函数包含两种,bufio.NewReaderbufio.NewReaderSize

  1. bufio.NewReaderSize

    func NewReaderSize(rd io.Reader, size int) *Reader {// 判断传入的rd是否已经是bufio.Reader类型的对象b, ok := rd.(*Reader)if ok && len(b.buf) >= size {// 如果是且其buf大于等于指定的size,直接返回return b}// 若size小于bufio中定义的minReadBufferSize,将其设为minReadBufferSize// minReadBufferSize = 16if size < minReadBufferSize {size = minReadBufferSize}// 利用new创建新的bufio.Reader对象r := new(Reader)// reset函数初始化bufio.Reader的所有域// 将buf设为大小为size的[]byte,将其中的io.Reader设为rdr.reset(make([]byte, size), rd)return r
    }
    
  2. bufio.NewReader

    func NewReader(rd io.Reader) *Reader {// 直接调用NewReaderSize,size设为默认的buffer size// defaultBufSize = 4096return NewReaderSize(rd, defaultBufSize)
    }
    

bufio.Reader.fill

该方法是 bufio.Reader 非常核心的方法之一,它将暂存有效(还没有被读取)的 buffer 数据左滑倒初始位置,并多次尝试从 rd 中读取新的字节,只要读取到长度n大于0,错误等于nil的数据,将其append到buffer中。

// fill reads a new chunk into the buffer.
// 这里的 new chunk 的大小是不一定的
func (b *Reader) fill() {// 将当前有效的数据左滑到初始位置if b.r > 0 {// 左滑copy(b.buf, b.buf[b.r:b.w])// 改变读取和写入标志位b.w -= b.rb.r = 0}// 若当前写入标志位已达到buffer尾端(即buffer是满的状态),panicif b.w >= len(b.buf) {panic("bufio: tried to fill full buffer")}// 开始读取新的数据:尝试有限次,maxConsecutiveEmptyReads = 100,即尝试100次for i := maxConsecutiveEmptyReads; i > 0; i-- {// 从 rd 中读取n, err := b.rd.Read(b.buf[b.w:])// 读取数小于0是一个致命的错误,进行panicif n < 0 {panic(errNegativeRead)}// w 右滑n位b.w += n// 若出现了错误,将当前错误设为err并返回if err != nil {b.err = errreturn}// 若读取字节数大于0,结束循环返回if n > 0 {return}// 若读取字节数等于0,继续尝试}// 若尝试100次之后仍然没有新的字节,将当前错误设为ErrNoProgress,并返回b.err = io.ErrNoProgress
}

要注意的是,bufio 在遇到错误时不会摒弃当前读到的字节,会继续将其存下来,并更新当前的error。

bufio.Reader.ReadByte

该方法非常简单,从 buffer 中读取一个字节并返回即可。

// ReadByte reads and returns a single byte.
// If no byte is available, returns an error.
func (b *Reader) ReadByte() (byte, error) {// 因为当前操作为 ReadByte 就会使得 UnreadRune 无效,所以需要将 lastRuneSize 设为-1b.lastRuneSize = -1// b.r == b.w 代表当前没有可以读取的字节// 这里反复比较 b.r 和 b.w,保证在读取时会有可读取的字节for b.r == b.w {// 若具有错误,返回0和其错误if b.err != nil {return 0, b.readErr()}// 调用fill方法读取新的字节b.fill() // buffer is empty}// 读取新的字节c := b.buf[b.r]// b.r 右滑一位b.r++// 此时调用 UnreadByte 是有效的,将 lastByte 设为 int(c)b.lastByte = int(c)return c, nil
}

注意 b.readErr 操作会读取当前的err并返回,同时会把当前的err设为nil:

func (b *Reader) readErr() error {err := b.errb.err = nilreturn err
}

bufio.Reader.UnreadByte

UnreadByte 会对读取字节的操作进行一个撤销操作,源码中是这么解释的:

// UnreadByte unreads the last byte. Only the most recently read byte can be unread.
//
// UnreadByte returns an error if the most recent method called on the
// Reader was not a read operation. Notably, Peek, Discard, and WriteTo are not
// considered read operations.

即只有在最近的操作能被撤销的时候,才能正常执行,否则会返回错误。

func (b *Reader) UnreadByte() error {// 操作无效的两种情况:// 1. 上一次操作不是可撤销的操作利用 b.lastByte 去判断// 2. 读标志位在初始位置,而写标志位已右移if b.lastByte < 0 || b.r == 0 && b.w > 0 {return ErrInvalidUnreadByte}// b.r > 0 || b.w == 0if b.r > 0 {// 这种情况下,只需将读标志位撤回一位,将上一个字节填充即可b.r--} else {// b.r == 0 && b.w == 0// 这种情况,将写标志位右移一位,将上一个字节填充到0位置即可b.w = 1}// 填充字节b.buf[b.r] = byte(b.lastByte)// 不能进行任何其他撤销的动作b.lastByte = -1b.lastRuneSize = -1return nil
}

bufio.Reader.ReadRune & bufio.Reader.UnreadRune

该两种操作于 ReadByteUnreadByte 基本类似,只是在读取的时候会有一些 UTF-8 编码到的问题,并且读取的不一定只是一个字节。

PS:关于 Rune 我以后还会专门写文档去介绍

bufio.Reader.Peek

Peek 顾名思义是一种 “瞄” 的操作,“只是去看看,不做任何变化”。Peek 会返回你指定个数的字节但是并不对 Reader 的内部状态做变化。其内部也有一些错误机制,在下面的源代码中进行解释:

func (b *Reader) Peek(n int) ([]byte, error) {// 字节数肯定不能小于0if n < 0 {return nil, ErrNegativeCount}// Peek 操作不能算是合法的读操作,所以不能进行撤销的操作b.lastByte = -1b.lastRuneSize = -1// 循环对 buffer 填充新的字节,直到以下条件中的任何一个满足://     1. 读取了 n 个字节//  2. 字节数大于了 buffer size// 3. 发生读取错误for b.w-b.r < n && b.w-b.r < len(b.buf) && b.err == nil {b.fill() // b.w-b.r < len(b.buf) => buffer is not full}// 若 n 大于 buffer size://     返回 r 到 w 的所有字节,和 buffer full 的错误//  即使不发生读取错误,也要解释为什么没有读到 n 个字节if n > len(b.buf) {return b.buf[b.r:b.w], ErrBufferFull}// 0 <= n <= len(b.buf)var err error// 若读取的字节数 avail 小于 n,将 n 设为 avail// 若没有发生读取错误,将 err 设为 ErrBufferFullif avail := b.w - b.r; avail < n {// not enough data in buffern = availerr = b.readErr()if err == nil {err = ErrBufferFull}}// 返回 r 到 r+n 的所有字节和 errreturn b.buf[b.r : b.r+n], err
}

bufio.Reader.Discard

Discard 即摒弃,其可以让我们指定摒弃未来的多少个字节。首先我们来看看一个方法 Buffered

func (b *Reader) Buffered() int { return b.w - b.r }

明显能看出来,这个方法返回的是当前缓存的字节的数量即当前可以读取的字节的数量。

其次,我们再来看看 Discard 方法的实现:

func (b *Reader) Discard(n int) (discarded int, err error) {// 明显,要摒弃的字节数不能小于0if n < 0 {return 0, ErrNegativeCount}// 也不能等于0,只是等于0没有错误返回if n == 0 {return}// Discard 跟 Peek 一样不是合法的读取操作,不能进行撤销b.lastByte = -1b.lastRuneSize = -1// remain:当前剩余需要Discard的字节的数量remain := n// 开始循环for {// 获取当前可以读取的字节数为skipskip := b.Buffered()// 若 skip == 0,fill填充bufferif skip == 0 {b.fill()// 填充后再次获取当前可以读取的字节数更新skipskip = b.Buffered()}// skip 应等于skip和remain中较小的一个if skip > remain {skip = remain}// 读标志位向前移skip位b.r += skip// remain减少skipremain -= skip// remain==0 代表已摒弃n个字节,可以正常返回if remain == 0 {return n, nil}// 如果发生错误// 返回摒弃的字节数,和错误if b.err != nil {return n - remain, b.readErr()}}
}

bufio.Reader.ReadSlice

ReadSlice 接受一个字节为参数,在buffer中寻找该字节,返回该字节之前的所有可读字节(以slice的形式)。源码中是这么解释 ReadSlice 的:

// ReadSlice reads until the first occurrence of delim in the input,
// returning a slice pointing at the bytes in the buffer.
// The bytes stop being valid at the next read.
// If ReadSlice encounters an error before finding a delimiter,
// it returns all the data in the buffer and the error itself (often io.EOF).
// ReadSlice fails with error ErrBufferFull if the buffer fills without a delim.
// Because the data returned from ReadSlice will be overwritten
// by the next I/O operation, most clients should use
// ReadBytes or ReadString instead.
// ReadSlice returns err != nil if and only if line does not end in delim.

ReadSlice 将参数 delim 作为边界读取字节,返回一个属于buffer的byte slice。返回值 slice 将在下一个读取操作后变为无效(因为其底部于buffer共享数组)。如果在遇到 delim 之前遇到错误,将返回所有数据和遇到的错误。如果在遇到 delim 之前 buffer 填满,将返回所有数据和 ErrBufferFull 错误。因为返回的数据有效期不长,所以不推荐用户使用该接口,推荐使用 ReadBytes 或 ReadString。

func (b *Reader) ReadSlice(delim byte) (line []byte, err error) {// 开始搜索的下标s := 0 // search start index// 开始循环for {// 利用 bytes.IndexByte 去在 buffer 中寻找 delim// 该方法在 delim 存在时返回 delim 下标,否则返回 -1// 如果返回值 i >= 0,代表当前的 buffer 中存在 delim,// 此时返回 buffer 中从 r 到 r+i+1 之间的字节即可if i := bytes.IndexByte(b.buf[b.r+s:b.w], delim); i >= 0 {i += sline = b.buf[b.r : b.r+i+1]b.r += i + 1break}// 若上述操作中 i == -1// 是否具有读取错误if b.err != nil {// 若是,返回所有数据和读取到的错误line = b.buf[b.r:b.w]b.r = b.werr = b.readErr()break}// buffer是否已填满if b.Buffered() >= len(b.buf) {// 若是,返回所有数据和ErrBufferFullb.r = b.wline = b.buferr = ErrBufferFullbreak}// s更新s = b.w - b.r // 不要去扫描已扫描过的区域// 填充bufferb.fill() // buffer is not full}// 因为该操作是一个合法的读操作// 在读取到字节之后,需要设置 lastByte 域// 使得可以进行UnreadByte操作if i := len(line) - 1; i >= 0 {b.lastByte = int(line[i])b.lastRuneSize = -1}return
}

bufio.Reader.collectFragments

此方法是一个在 ReadSlice 和其他 ReadBytes、ReadString等方法之间工作的中间方法。其循环调用 ReadSlice 方法直到遇到 delim 为止,并在过程中记录获取的所有 slice。该方法有四个返回值分别是:

  • fullBuffers:一个二维的字节slice,每次发生buffer full错误时,对buffer进行一次copy,并存到fullBuffers中。
  • finalFragment:在遇到 delim 之前读到的最后一串字节
  • totalLen:读取的总长度
  • err:发生的错误
func (b *Reader) collectFragments(delim byte) (fullBuffers [][]byte, finalFragment []byte, totalLen int, err error) {var frag []byte// 开始循环for {var e error// 调用 ReadSlice 方法frag, e = b.ReadSlice(delim)// 若返回没有错误,意味着读取到了 delim 字节,断开循环// 此时的 frag 就等于最终的 finalFragmentif e == nil { // got final fragmentbreak}// 若返回的错误不等于buffer已满,意味着发生了读取错误,停止循环if e != ErrBufferFull { // unexpected errorerr = ebreak}// 到这,就代表没有读取到 delim 并且 buffer 已填满// 此时,我们需要对 buffer 做一个复制,将其存到 fullbuffers 当中buf := make([]byte, len(frag))copy(buf, frag)fullBuffers = append(fullBuffers, buf)totalLen += len(buf)}// 在最终长度上添加 finalFragment 的长度totalLen += len(frag)return fullBuffers, frag, totalLen, err
}

除了上述的所有方法之外,bufio.Reader 还有 ReadBytes、ReadString 等方法,其内部依赖 ReadSlice 和 collectFragments 的实现,实现方法比较简单,就留给大家自己去看了。

PS:如果需要关于 bufio.Writer 的文档请留言

Golang bufio Reader 源码详解相关推荐

  1. Go 语言 bytes.Buffer 源码详解之1

    转载地址:Go 语言 bytes.Buffer 源码详解之1 - lifelmy的博客 前言 前面一篇文章 Go语言 strings.Reader 源码详解,我们对 strings 包中的 Reade ...

  2. Go bufio.Reader 结构+源码详解

    转载地址:Go bufio.Reader 结构+源码详解 I - lifelmy的博客 前言 前面的两篇文章 Go 语言 bytes.Buffer 源码详解之1.Go 语言 bytes.Buffer ...

  3. Go bufio.Reader 结构+源码详解 I

    你必须非常努力,才能看起来毫不费力! 微信搜索公众号[ 漫漫Coding路 ],一起From Zero To Hero ! 前言 前面的两篇文章 Go 语言 bytes.Buffer 源码详解之1,G ...

  4. 【JAVA秘籍心法篇-Spring】Spring XML解析源码详解

    [JAVA秘籍心法篇-Spring]Spring XML解析源码详解 所谓天下武功,无坚不摧,唯快不破.但有又太极拳法以快制慢,以柔克刚.武功外式有拳打脚踢,刀剑棍棒,又有内功易筋经九阳神功.所有外功 ...

  5. Android AR开发实践之七:OpenGLES相机预览背景绘制源码详解

    Android AR开发实践之七:OpenGLES相机预览背景绘制源码详解 目录 Android AR开发实践之七:OpenGLES相机预览背景绘制源码详解 一.OpenGL ES渲染管线 1.基本处 ...

  6. 【Live555】live555源码详解(九):ServerMediaSession、ServerMediaSubsession、live555MediaServer

    [Live555]live555源码详解系列笔记 继承协作关系图 下面红色表示本博客将要介绍的三个类所在的位置: ServerMediaSession.ServerMediaSubsession.Dy ...

  7. 【Live555】live555源码详解系列笔记

    [Live555]liveMedia下载.配置.编译.安装.基本概念 [Live555]live555源码详解(一):BasicUsageEnvironment.UsageEnvironment [L ...

  8. 【Live555】live555源码详解(八):testRTSPClient

    [Live555]live555源码详解系列笔记 继承协作关系图 下面红色表示本博客将要介绍的testRTSPClient实现的三个类所在的位置: ourRTSPClient.StreamClient ...

  9. 【Live555】live555源码详解(七):GenericMediaServer、RTSPServer、RTSPClient

    [Live555]live555源码详解系列笔记 继承协作关系图 下面红色表示本博客将要介绍的三个类所在的位置: GenericMediaServer.RTSPServer.RTSPClient 14 ...

最新文章

  1. The NVIDIA driver on your system is too old
  2. shell编程系列20--文本处理三剑客之awk常用选项
  3. Powershell About Active Directory Group Membership of a domain user
  4. servlet多重映射_【简答题】请简要概述什么是Servlet的多重映射,并列出Servlet多重映射的实现方式。...
  5. nginx代理php不能跳转页面,nginx 解决首页跳转问题详解
  6. 百度全景地图 -(街景)_百度地图VR全景,世界触手可及
  7. 16g电脑内存有什么好处_16G电脑运行内存可以达到什么样子。
  8. 【论文简述及翻译】A Large Dataset to Train Convolutional Networks for Disparity, Optical Flow, and SceneFlow
  9. C++学习路线图(重整理)
  10. 民航票务管理系统-C语言--录入,查询,订票,退票,修改航班信息以及主菜单和子菜单。
  11. 如何把pdf转换成excel
  12. 关于大成资源网这一个月大成网停更详细原因
  13. 为自动驾驶保驾护航—谈谈主流中间件设计
  14. java 19位时间戳_Java将19位Unix时间戳转换为可读日期
  15. 锐捷ac怎么发现局域网ap_锐捷AC配置步骤备忘
  16. js 排班插件_排班小程序
  17. 小记一次海量数据实时查询域名库设计(上)
  18. 优麒麟服务器配置备忘
  19. js给img的src赋值
  20. 拥有普通的人平凡 幸福和英雄般坚持---Leo读 不是孙振耀写的职场感言 4

热门文章

  1. Windows10系统中怎么使用32位IE浏览器?
  2. 神经网络收敛是什么意思,算法的收敛速度的计算
  3. 加密之数字证书和PFX信息交换
  4. 什么是用户运营?每个阶段该怎么运营?
  5. 如何提升数据敏感度、数据分析思维、数据分析能力?
  6. TCAD(technology computer aided design)学习笔记
  7. caged系统pdf_货代常用英文
  8. 电脑录视频用什么软件最好?录像软件,3大工具推荐!
  9. gt、gte、lt、lte、eq、neq缩写含义
  10. Overload 和Override 的区别。