45

go - bufio 缓冲读写详解级实例

 4 years ago
source link: https://www.tuicool.com/articles/vYzuueq
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

go 在提供了 io 包的同时也提供了 bufio 包来实现有缓存的读写操作以提高读写性能。为什么 bufio 性能比 io 高呢?

缓冲读写

缓冲读

// 默认缓冲区大小
const (
    defaultBufSize = 4096
)

// 最小缓冲区大小 自定义小于次阈值将会被覆盖
const minReadBufferSize = 16

// 使用默认缓冲区大小
bufio.NewReader(rd io.Reader)
// 使用自定义缓冲区大小
bufio.NewReaderSize(rd io.Reader, size int)

缓冲读 的大致过程如下,设定好缓冲区大小 buf_size 后,读取的字节数为 rn ,缓冲的字节数为 bn

  1. 如果缓冲区为空,且 rn >= buf_size ,则直接从文件读取,不启用缓冲。
  2. 如果缓冲区为空,且 rn < buf_size ,则从文件读取 buf_size 字节的内容到缓冲区,程序再从缓冲区中读取 rn 字节的内容,此时缓冲区剩余 bn = buf_size - rn 字节。
  3. 如果缓冲区不为空, rn < bn ,则从缓冲区读取 rn 字节的内容,不发生文件 IO
  4. 如果缓冲区不为空, rn >= bn ,则从缓冲区读取 bn 字节的内容,不发生文件 IO ,缓冲区置为空,回归 1/2 步骤。

缓冲读通过预读,可以在一定程度上减少文件 IO 次数,故提高性能。

代码演示:

package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    // 用 strings.Reader 模拟一个文件IO对象
    strReader := strings.NewReader("12345678901234567890123456789012345678901234567890")
    
    // go 的缓冲区最小为 16 byte,我们用最小值比较容易演示
    bufReader := bufio.NewReaderSize(strReader, 16)

    // bn = 0 但 rn >= buf_size 缓冲区不启用 发生文件IO
    tmpStr := make([]byte, 16)
    n, _ := bufReader.Read(tmpStr)
    // bufReader buffered: 0, content: 1234567890123456
    fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n])

    // bn = 0 rn < buf_size 缓冲区启用
    // 缓冲区从文件读取 buf_size 字节 发生文件IO
    // 程序从缓冲区读取 rn 字节
    // 缓冲区剩余 bn = buf_size - rn 字节
    tmpStr = make([]byte, 15)
    n, _ = bufReader.Read(tmpStr)
    // bufReader buffered: 1, content: 789012345678901
    fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n])

    // bn = 1 rn > bn 
    // 程序从缓冲区读取 bn 字节 缓冲区置空 不发生文件IO
    // 注意这里只能读到一个字节
    tmpStr = make([]byte, 10)
    n, _ = bufReader.Read(tmpStr)
    // bufReader buffered: 0, content: 2
    fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n])

    // bn = 0 rn < buf_size 启用缓冲读 发生文件IO
    // 缓冲区从文件读取 buf_size 字节
    // 程序从缓冲区读取 rn 字节
    // 缓冲区剩余 bn = buf_size - rn 字节
    tmpStr = make([]byte, 10)
    n, _ = bufReader.Read(tmpStr)
    // bufReader buffered: 6, content: 3456789012
    fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n])

    // bn = 6 rn <= bn
    // 则程序冲缓冲区读取 rn 字节 不发生文件IO
    tmpStr = make([]byte, 3)
    n, _ = bufReader.Read(tmpStr)
    // bufReader buffered: 3, content: 345
    fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n])
    
    // bn = 3 rn <= bn
    // 则程序冲缓冲区读取 rn 字节 不发生文件IO
    tmpStr = make([]byte, 3)
    n, _ = bufReader.Read(tmpStr)
    // bufReader buffered: 0, content: 678
    fmt.Printf("bufReader buffered: %d, content: %s\n", bufReader.Buffered(), tmpStr[:n])
}

要注意的是当缓冲区中有内容时,程序的此次读取都会从缓冲区读,而不会发生文件IO。只有当缓冲区为空时,才会发生文件IO。如果缓冲区的大小足够,则启用缓冲读,先将内容载入填满缓冲区,程序再从缓冲区中读取。如果缓冲区过小,则会直接从文件读取,而不使用缓冲读。

缓冲写

// 使用 defaultBufSize 大小
func NewWriter(w io.Writer)
// 如果 size <= 0 则使用 defaultBufSize
func NewWriterSize(w io.Writer, size int)

缓冲写 的大致过程如下,设定好缓冲区大小 buf_size 后,写入的字节数为 wn ,缓冲的字节数为 bn

  1. 如果缓冲区为空,且 wn >= buf_size ,则直接写入文件,不启用缓冲,发生文件IO。
  2. 如果缓冲区为空,且 wn < buf_size ,则程序将内容写入缓冲区,不发生文件IO。
  3. 如果缓冲区不为空, wn + bn < buf_size ,则程序将内容写入缓冲区,不发生文件IO。
  4. 如果缓冲区不为空, wn + bn >= buf_size ,则缓冲区将 buf_size 字节内容写入文件,缓冲区 wn + bn - buf_size 的剩余内容。

简单说就是要写入的内容先缓冲着,缓冲不下了则将缓冲区内容写入文件。

代码演示:

package main

import (
    "bufio"
    "fmt"
    "io"
    "strings"
)

// 自定义一个 io.Writer 对象
type StringWriter struct {
}

func (s StringWriter) Write(p []byte) (n int, err error) {
    fmt.Printf("io write: %s\n", p)
    return len(p), nil
}

func main() {
    strReader := strings.NewReader("12345678901234567890")
    bufReader := bufio.NewReader(strReader)

    // 自定义的 io.Writer 对象
    var stringWriter io.Writer
    stringWriter = StringWriter{}

    // 写缓冲大小为 6 为了更好的演示我们自定义了一个 io.Writer
    bufWriter := bufio.NewWriterSize(stringWriter, 6)

    tmpStr := make([]byte, 8)
    for true {
        rn, err := bufReader.Read(tmpStr)
        if nil != err && io.EOF == err {
            break
        }

        _, err = bufWriter.Write(tmpStr[:rn])
        fmt.Printf("\nread and write: %s\n", tmpStr[:rn])
        fmt.Printf("bufWriter buffered: %d, available: %d, size: %d\n", bufWriter.Buffered(), bufWriter.Available(), bufWriter.Size())
        fmt.Printf("----------------------\n")
    }

    // 缓冲区最后剩余的一些内容
    _ = bufWriter.Flush()
}
go run main.go
// 没有发生写动作 '1234' 被缓冲
read and write: 1234
bufWriter buffered: 4, available: 2, size: 6
----------------------
io write: 123456
// '12345678' 缓冲区满 则会写 '123456',继续缓冲 '78'
read and write: 5678
bufWriter buffered: 2, available: 4, size: 6
----------------------
// 没有发生写动作 '789012' 被缓冲
read and write: 9012
bufWriter buffered: 6, available: 0, size: 6
----------------------
io write: 789012
// '7890123456' 缓冲区满 则会写 '789012',继续缓冲 '3456'
read and write: 3456
bufWriter buffered: 4, available: 2, size: 6
----------------------
io write: 345678
// '34567890' 缓冲区满 则会写 '345678',继续缓冲 '90'
read and write: 7890
bufWriter buffered: 2, available: 4, size: 6
----------------------
// 文件读取完毕,输出剩余的 '90'
io write: 90

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK