45
go - bufio 缓冲读写详解级实例
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
:
-
如果缓冲区为空,且
rn >= buf_size
,则直接从文件读取,不启用缓冲。 -
如果缓冲区为空,且
rn < buf_size
,则从文件读取buf_size
字节的内容到缓冲区,程序再从缓冲区中读取rn
字节的内容,此时缓冲区剩余bn = buf_size - rn
字节。 -
如果缓冲区不为空,
rn < bn
,则从缓冲区读取rn
字节的内容,不发生文件IO
。 -
如果缓冲区不为空,
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
:
-
如果缓冲区为空,且
wn >= buf_size
,则直接写入文件,不启用缓冲,发生文件IO。 -
如果缓冲区为空,且
wn < buf_size
,则程序将内容写入缓冲区,不发生文件IO。 -
如果缓冲区不为空,
wn + bn < buf_size
,则程序将内容写入缓冲区,不发生文件IO。 -
如果缓冲区不为空,
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
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK