go - bufio 緩衝讀寫詳解級實例

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章