Go bufio

JunChow520 · · 715 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

bufio通过缓存来提高效率,缓存放在主存中。

Golang的bufio包实现了带缓存的I/O读写操作,用来帮助处理I/O缓存。

通过缓存可以提高效率,把文件读取进缓存(内存)后再读取的时候可避免文件系统的I/O,从而提高速度。同理,当进行写操作时会先把文件写入缓存(内存),然后由缓存写入文件系统。缓冲区的设计目的是为了存储多次的写入,最后一次性将缓冲区内容写入文件。

缓冲原理

为什么bufio性能会比io性能要高呢?

以缓存读为例,当设置好缓冲区大小以及读取字节数和缓冲字节数后。

  • 若缓冲区为空,同时读取字节数大于等于缓冲字节数,则直接从文件中读取,即不启用缓冲。
  • 若缓冲区为空,同时读取字节数小于缓冲区字节数,则从文件中读取缓冲区字节内容到缓冲区后,程序再从缓冲区中读取内容,此时缓冲区大小会等于缓冲区大小减去缓冲区字节数。
  • 若缓冲区不为空,同时读取字节数小于缓冲字节数,则会从缓冲区读取内容,此时不会发生文件I/O。
  • 若缓冲区不为空,同时读取字节数大于等于缓冲字节数,则会置空缓冲区,重新读取。

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

以缓冲写为例,当设置为缓冲区大小、写入字节数、缓冲字节数。

缓冲区 缓冲写 文件I/O 描述
写入字节数 >= 缓冲字节数 直接写入文件,不启用缓冲
写入字节数 < 缓冲字节数 写入缓冲区
非空 写入字节数 + 缓冲字节数 < 缓冲区大小 写入缓冲区
非空 写入字节数 + 缓冲字节数 >= 缓冲区大小 将缓冲区字节写入文件

bufio

bufio包封装了io.Readerio.Writer对象,具有缓存和文本读写功能。

bufio包提供了两个New函数NewReader()NewWriter(),分别在任意io.Readerio.Writer的基础上再包装一层缓存区得到bufio.Readerbufio.Writer

func bufio.NewReader(rd io.Reader) *bufio.Reader
func bufio.NewWriter(w io.Writer) *bufio.Writer
对象 描述
bufio.Reader 带缓存区的字节流读取器
bufio.Writer 带缓存区的字节流写入器

可用于文件I/O操作,先将数据缓存到内存中,再整体做文件I/O操作,尽最大可能地减少磁盘I/O。

bufio.Reader

通过bufio.Reader可从底层的io.Reader中更大批量地读取数据,使读取操作减少。

若数据读取时的块数量是固定合适的,底层媒体设备将会有更好的表现,也因此会提高程序的性能。

io.Reader --> buffer --> consumer

假如:消费者想要从磁盘上读取10个字符,每次读取一个字符。在底层实现上会触发10次读取操作。若磁盘按每个数据块4个字节来读取数据,则bufio.Reader会起到帮助作用。底层引擎会存储整个数据块,然后提供一个可以挨个读取字节的API给消费者。

// 缓存读取器
type Reader struct {
    buf             []byte      // 缓存
    rd              io.Reader   // 底层的io.Reader 
    r, w            int
    err             error       // 读过程中遇到的错误
    lastByte        int         // 最后一次读到的字节      
    lastRuneSize    int         // 最后一次读到的Rune的大小  
}
字段 类型 描述
buf []byte 缓存
fd io.Reader 消费者,读取器。
err error 缓存读取时错误
lastByte int 最后一次读取到的字节
lastRuneSize int 最后一次读取到的Rune大小

bufio.NewReaderSize

func bufio.NewReaderSize(rd io.Reader, size int) *bufio.Reader
  • NewReaderSize会将rd封装成一个带缓存的buf.Reader对象
  • 缓存大小由size指定,若小于16字节则会被设置为16。
  • 如果rd的基类型是拥有足够缓存的bufio.Reader类型则直接将rd转换为基类型后返回。

bufio.NewReader

  • bufio.NewReader()相当于bufio.NewReaderSize(rd, 4096)
func bufio.NewReader(rd io.Reader) *bufio.Reader

reader.Read

  • bufio.Read(p []byte)相当于读取大小为len(p)的内容
func (b *Reader) Read(p []byte) (n int, err error) 

读取思路

  1. buf缓存区有内容时,将缓存区内容全部填入p并清空缓存区。
  2. buf缓存区没内容时
    2.1. len(p) > len(buf),即待读取内容比缓存区要大,直接去文件读取即可。
    2.2. len(p) < len(buf),即待读取内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满,此时缓存区有剩余内容。
  3. 再次读取时缓存有内容,则将缓存区内容全部填入p并清空缓存区。
  • 缓冲读通过预读,可以在一定程序上减少文件I/O次数以提高性能。
//原始字符串
str := "12345678901234567890123456789012345678901234567890"

//将字符串转换为流式I/O对象
strReader := strings.NewReader(str)

//设置读缓存区大小
bufReader := bufio.NewReaderSize(strReader, 16)//16byte

//缓存读
p := make([]byte, 16)
n,err := bufReader.Read(p)
if err!=nil{
    panic(err)
}

buffered := bufReader.Buffered()
fmt.Printf("buffered:%d, content:%s\n", buffered, p[:n])
buffered:0, content:1234567890123456

当缓存区中有内容时,程序的读取会从缓存区读而不会发生文件I/O。只有当缓存区为空时才会发生文件I/O

若缓存区的大小足够则启用缓存读,先会将内容载入填满缓存区,程序再从缓存区中读取。若缓存区过小则会直接从文件中读取而不使用缓存读

bufio.Writer

多次少量写操作会影响程序性能,因为每次写操作最终都会体现为系统层调用,频繁写操作可能对CPU造成伤害。而且很多硬件设备更适合处理块对齐的数据,比如硬盘。

为减少多次写操作所需的开支,Golang提供了bufio.Writer

type Writer struct {
    err error
    buf []byte//缓存存储数据
    n   int//缓存内部当前操作的位置
    wr  io.Writer//消费者写入器
}
字段 类型 描述
err error I/O错误
buf []byte 缓存,存储数据。
n int 缓存内部当前操作的位置
wr io.Writer 消费者,写入器。

使用bufio.Writer将不再直接写入目的地(实现了io.Writer接口),而是先写入缓存,当缓存写满后再统一写入目的地。

producer --> buffer --> io.Writer

bufio.Writer底层使用[]byte进行缓存,字段buf用于存储数据,当缓存满或Flush被调用时,消费者wr可以从缓存中读取到数据。若写入过程中发生了I/O error,此error将会被赋给err字段,error发生之后,writer将会停止操作(writer is no-op)。

bufio.Writer默认使用4096长度字节的缓存,可使用NewWriterSize()方法来设置。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:JunChow520

查看原文:Go bufio

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

715 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传