Go 语言标准库 bufio 详解

2023-04-06 13:29:48 浏览数 (1)

01

介绍

Go 语言标准库 bufio 是基于 Go 语言标准库 io 实现的,查看源码可以发现,实际上它是包装了 io.Reader 接口和 io.Writer 接口,并且实现它们。

bufio 顾名思义,就是在缓冲区读写数据,比直接读写文件或网络中的数据,性能更好些。

本文我们介绍 bufio 的相关内容,建议读者朋友们最好是先了解一下 io 的相关内容。

02

标准库 bufio 的数据类型

查看标准库 `bufio` 的文档[1],它的数据类型主要有 bufio.Readerbufio.Writerbufio.ReadWriterbufio.Scanner

我们以 bufio.Reader 为例,介绍它的数据结构、初始化方式和提供的方法。

bufio.Reader 的数据结构:

代码语言:javascript复制
type Reader struct {
 buf          []byte
 rd           io.Reader
 r, w         int
 err          error
 lastByte     int
 lastRuneSize int
}

阅读源码,我们可以发现 bufio.Reader 中包含的字段:

  • buf []byte 缓冲区。
  • rd io.Reader 缓冲区的数据源。
  • r,w int 缓冲区读写索引位置。
  • err error 错误。
  • lastByte int 未读字节的上一个字节。
  • lastRuneSize 未读字符的上一个字符的大小。

bufio.Reader 的初始化方式:

使用 bufio.Reader 时,需要先初始化,bufio 包提供了两个初始化的函数,分别是 NewReaderSizeNewReader

代码语言:javascript复制
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 {
  size = minReadBufferSize
 }
 r := new(Reader)
 r.reset(make([]byte, size), rd)
 return r
}

func NewReader(rd io.Reader) *Reader {
 return NewReaderSize(rd, defaultBufSize)
}

阅读源码,我们可以发现这两个函数的返回值都是 *bufio.Reader 类型。

其中 NewReader 是包装了 NewReaderSize 函数,给定了一个默认值 4096,设置读缓冲区的大小。

如果我们使用默认值,一般选择使用 NewReader 函数。

如果不想使用默认值,可以选择使用 NewReaderSize 函数。

bufio.Reader 提供的方法:

bufio.Reader 提供了 15 个方法,我们介绍两个比较常用的方法,分别是 ReadReadBytes

代码语言:javascript复制
func (b *Reader) Read(p []byte) (n int, err error) {
 // 省略代码 ...
 if b.r == b.w {
  if b.err != nil {
   return 0, b.readErr()
  }
  if len(p) >= len(b.buf) {
   // Large read, empty buffer.
   // Read directly into p to avoid copy.
   n, b.err = b.rd.Read(p)
   if n < 0 {
    panic(errNegativeRead)
   }
   if n > 0 {
    b.lastByte = int(p[n-1])
    b.lastRuneSize = -1
   }
   return n, b.readErr()
  }
  // 省略代码 ...
  b.w  = n
 }

 // copy as much as we can
 // Note: if the slice panics here, it is probably because
 // the underlying reader returned a bad count. See issue 49795.
 n = copy(p, b.buf[b.r:b.w])
 b.r  = n
 b.lastByte = int(b.buf[b.r-1])
 b.lastRuneSize = -1
 return n, nil
}

阅读源码,我们可以发现 Read 方法是将缓冲区中的数据,读取到 p 中,并返回读取的字节大小和错误。

代码语言:javascript复制
func (b *Reader) ReadBytes(delim byte) ([]byte, error) {
 full, frag, n, err := b.collectFragments(delim)
 // Allocate new buffer to hold the full pieces and the fragment.
 buf := make([]byte, n)
 n = 0
 // Copy full pieces and fragment in.
 for i := range full {
  n  = copy(buf[n:], full[i])
 }
 copy(buf[n:], frag)
 return buf, err
}

阅读源码,我们可以发现 ReadBytes 方法是读取缓冲区中的数据截止到分隔符 delim 的位置,并返回数据和错误。

使用示例:

Read 方法

代码语言:javascript复制
func main() {
 f, _ := os.Open("/Users/frank/GolandProjects/go-package/lesson14/file.txt")
 defer f.Close()
 r := bufio.NewReader(f)
 p := make([]byte, 12)
 index, _ := r.Read(p)
 fmt.Println(index)
 fmt.Println(string(p[:index]))
}

需要注意的是,p 字节切片的长度,一个中文字符是 3 个字节,一个英文字符是 1 个字节。

ReadBytes 方法

代码语言:javascript复制
func main() {
 f, _ := os.Open("/Users/frank/GolandProjects/go-package/lesson14/file.txt")
 defer f.Close()
 r := bufio.NewReader(f)
  bs, _ := r.ReadBytes('n')
 fmt.Println(string(bs))
}

需要注意的是,分隔符参数是 byte 类型,使用单引号。

03

总结

本文我们以 bufio.Reader 为例,介绍标准库 bufio 的数据类型、初始化方式和提供的方法。

实际上标准库 bufio 使用非常简单,但是想要避免踩 “坑”,读者朋友们最好是熟读标准库 `bufio` 的源码[2]

参考资料

[1]

标准库 bufio 的文档: https://pkg.go.dev/bufio@go1.20.2

[2]

标准库 bufio 的源码: https://cs.opensource.google/go/go/ /refs/tags/go1.20.2:src/bufio/

0 人点赞