GO語言基礎進階教程:bufio包

原文鏈接:https://zhuanlan.zhihu.com/p/73690883 作者:茹姐

生命不止,繼續Go go go。。

Go語言在io操作中,還提供了一個bufio的包,使用這個包可以大幅提高文件讀寫的效率。

一、bufio包原理

bufio 是通過緩衝來提高效率。

io操作本身的效率並不低,低的是頻繁的訪問本地磁盤的文件。所以bufio就提供了緩衝區(分配一塊內存),讀和寫都先在緩衝區中,最後再讀寫文件,來降低訪問本地磁盤的次數,從而提高效率。

簡單的說就是,把文件讀取進緩衝(內存)之後再讀取的時候就可以避免文件系統的io 從而提高速度。同理,在進行寫操作時,先把文件寫入緩衝(內存),然後由緩衝寫入文件系統。看完以上解釋有人可能會表示困惑了,直接把 內容->文件 和 內容->緩衝->文件相比, 緩衝區好像沒有起到作用嘛。其實緩衝區的設計是爲了存儲多次的寫入,最後一口氣把緩衝區內容寫入文件。

bufio 封裝了io.Reader或io.Writer接口對象,並創建另一個也實現了該接口的對象。

io.Reader或io.Writer 接口實現read() 和 write() 方法,對於實現這個接口的對象都是可以使用這兩個方法的。

Reader對象

bufio.Reader 是bufio中對io.Reader 的封裝

// Reader implements buffering for an io.Reader object.
type Reader struct {
    buf          []byte
    rd           io.Reader // reader provided by the client
    r, w         int       // buf read and write positions
    err          error
    lastByte     int // last byte read for UnreadByte; -1 means invalid
    lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}

bufio.Read(p []byte) 相當於讀取大小len§的內容,思路如下:

1.當緩存區有內容的時,將緩存區內容全部填入p並清空緩存區
2.當緩存區沒有內容的時候且len§>len(buf),即要讀取的內容比緩存區還要大,直接去文件讀取即可
3.當緩存區沒有內容的時候且len§<len(buf),即要讀取的內容比緩存區小,緩存區從文件讀取內容充滿緩存區,並將p填滿(此時緩存區有剩餘內容)
4.以後再次讀取時緩存區有內容,將緩存區內容全部填入p並清空緩存區(此時和情況1一樣)

源碼:

// Read reads data into p.
// It returns the number of bytes read into p.
// The bytes are taken from at most one Read on the underlying Reader,
// hence n may be less than len(p).
// To read exactly len(p) bytes, use io.ReadFull(b, p).
// At EOF, the count will be zero and err will be io.EOF.
func (b *Reader) Read(p []byte) (n int, err error) {
    n = len(p)
    if n == 0 {
        return 0, b.readErr()
    }
    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()
        }
        // One read.
        // Do not use b.fill, which will loop.
        b.r = 0
        b.w = 0
        n, b.err = b.rd.Read(b.buf)
        if n < 0 {
            panic(errNegativeRead)
        }
        if n == 0 {
            return 0, b.readErr()
        }
        b.w += n
    }// copy as much as we can
    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
}


說明:

reader內部通過維護一個r, w 即讀入和寫入的位置索引來判斷是否緩存區內容被全部讀出。

Writer對象

bufio.Writer 是bufio中對io.Writer 的封裝

// Writer implements buffering for an io.Writer object.
// If an error occurs writing to a Writer, no more data will be
// accepted and all subsequent writes, and Flush, will return the error.
// After all data has been written, the client should call the
// Flush method to guarantee all data has been forwarded to
// the underlying io.Writer.
type Writer struct {
    err error
    buf []byte
    n   int
    wr  io.Writer
}

bufio.Write(p []byte) 的思路如下

1.判斷buf中可用容量是否可以放下 p
2.如果能放下,直接把p拼接到buf後面,即把內容放到緩衝區
3.如果緩衝區的可用容量不足以放下,且此時緩衝區是空的,直接把p寫入文件即可
4.如果緩衝區的可用容量不足以放下,且此時緩衝區有內容,則用p把緩衝區填滿,把緩衝區所有內容寫入文件,並清空緩衝區
5.判斷p的剩餘內容大小能否放到緩衝區,如果能放下(此時和步驟1情況一樣)則把內容放到緩衝區
6.如果p的剩餘內容依舊大於緩衝區,(注意此時緩衝區是空的,情況和步驟3一樣)則把p的剩餘內容直接寫入文件
以下是源碼

// Write writes the contents of p into the buffer.
// It returns the number of bytes written.
// If nn < len(p), it also returns an error explaining
// why the write is short.
func (b *Writer) Write(p []byte) (nn int, err error) {
    for len(p) > b.Available() && b.err == nil {
        var n int
        if b.Buffered() == 0 {
            // Large write, empty buffer.
            // Write directly from p to avoid copy.
            n, b.err = b.wr.Write(p)
        } else {
            n = copy(b.buf[b.n:], p)
            b.n += n
            b.Flush()
        }
        nn += n
        p = p[n:]
    }
    if b.err != nil {
        return nn, b.err
    }
    n := copy(b.buf[b.n:], p)
    b.n += n
    nn += n
    return nn, nil
}


說明:

b.wr 存儲的是一個io.writer對象,實現了Write()的接口,所以可以使用b.wr.Write§ 將p的內容寫入文件。

b.flush() 會將緩存區內容寫入文件,當所有寫入完成後,因爲緩存區會存儲內容,所以需要手動flush()到文件。

b.Available() 爲buf可用容量,等於len(buf) - n。

下圖解釋的是其中一種情況,即緩存區有內容,剩餘p大於緩存區

二、bufio包

bufio包實現了有緩衝的I/O。它包裝一個io.Reader或io.Writer接口對象,創建另一個也實現了該接口,且同時還提供了緩衝和一些文本I/O的幫助函數的對象。

bufio.Reader:

bufio.Reader 實現瞭如下接口: io.Reader io.WriterTo io.ByteScanner io.RuneScanner

// NewReaderSize 將 rd 封裝成一個帶緩存的 bufio.Reader 對象,
// 緩存大小由 size 指定(如果小於 16 則會被設置爲 16)。
// 如果 rd 的基類型就是有足夠緩存的 bufio.Reader 類型,則直接將
// rd 轉換爲基類型返回。
func NewReaderSize(rd io.Reader, size int) *Reader
​
// NewReader 相當於 NewReaderSize(rd, 4096)
func NewReader(rd io.Reader) *Reader
​
// Peek 返回緩存的一個切片,該切片引用緩存中前 n 個字節的數據,
// 該操作不會將數據讀出,只是引用,引用的數據在下一次讀取操作之
// 前是有效的。如果切片長度小於 n,則返回一個錯誤信息說明原因。
// 如果 n 大於緩存的總大小,則返回 ErrBufferFull。
func (b *Reader) Peek(n int) ([]byte, error)// Read 從 b 中讀出數據到 p 中,返回讀出的字節數和遇到的錯誤。
// 如果緩存不爲空,則只能讀出緩存中的數據,不會從底層 io.Reader
// 中提取數據,如果緩存爲空,則:
// 1、len(p) >= 緩存大小,則跳過緩存,直接從底層 io.Reader 中讀
// 出到 p 中。
// 2、len(p) < 緩存大小,則先將數據從底層 io.Reader 中讀取到緩存
// 中,再從緩存讀取到 p 中。
func (b *Reader) Read(p []byte) (n int, err error)// Buffered 返回緩存中未讀取的數據的長度。
func (b *Reader) Buffered() int// ReadBytes 功能同 ReadSlice,只不過返回的是緩存的拷貝。
func (b *Reader) ReadBytes(delim byte) (line []byte, err error)// ReadString 功能同 ReadBytes,只不過返回的是字符串。
func (b *Reader) ReadString(delim byte) (line string, err error)...

bufio.Writer:

bufio.Writer 實現瞭如下接口: io.Writer io.ReaderFrom io.ByteWriter

// NewWriterSize 將 wr 封裝成一個帶緩存的 bufio.Writer 對象,
// 緩存大小由 size 指定(如果小於 4096 則會被設置爲 4096)。
// 如果 wr 的基類型就是有足夠緩存的 bufio.Writer 類型,則直接將
// wr 轉換爲基類型返回。
func NewWriterSize(wr io.Writer, size int) *Writer
​
// NewWriter 相當於 NewWriterSize(wr, 4096)
func NewWriter(wr io.Writer) *Writer
​
// WriteString 功能同 Write,只不過寫入的是字符串
func (b *Writer) WriteString(s string) (int, error)// WriteRune 向 b 寫入 r 的 UTF-8 編碼,返回 r 的編碼長度。
func (b *Writer) WriteRune(r rune) (size int, err error)// Flush 將緩存中的數據提交到底層的 io.Writer 中
func (b *Writer) Flush() error// Available 返回緩存中未使用的空間的長度
func (b *Writer) Available() int// Buffered 返回緩存中未提交的數據的長度
func (b *Writer) Buffered() int// Reset 將 b 的底層 Writer 重新指定爲 w,同時丟棄緩存中的所有數據,復位
// 所有標記和錯誤信息。相當於創建了一個新的 bufio.Writer。
func (b *Writer) Reset(w io.Writer)...

三、實例代碼

讀取數據:

package main
​
import (
    "os"
    "fmt"
    "bufio"
)func main() {
    /*
    bufio:高效io讀寫
        buffer緩存
        io:input/output
​
    將io包下的Reader,Write對象進行包裝,帶緩存的包裝,提高讀寫的效率
​
        ReadBytes()
        ReadString()
        ReadLine()
​
     */
​
     fileName:="/Users/ruby/Documents/pro/a/english.txt"
     file,err := os.Open(fileName)
     if err != nil{
        fmt.Println(err)
        return
     }
     defer file.Close()//創建Reader對象
     //b1 := bufio.NewReader(file)
     //1.Read(),高效讀取
     //p := make([]byte,1024)
     //n1,err := b1.Read(p)
     //fmt.Println(n1)
     //fmt.Println(string(p[:n1]))//2.ReadLine()
     //data,flag,err := b1.ReadLine()
     //fmt.Println(flag)
     //fmt.Println(err)
     //fmt.Println(data)
     //fmt.Println(string(data))//3.ReadString()
    // s1,err :=b1.ReadString('\n')
    // fmt.Println(err)
    // fmt.Println(s1)
    //
    // s1,err = b1.ReadString('\n')
    // fmt.Println(err)
    // fmt.Println(s1)
    //
    //s1,err = b1.ReadString('\n')
    //fmt.Println(err)
    //fmt.Println(s1)
    //
    //for{
    //  s1,err := b1.ReadString('\n')
    //  if err == io.EOF{
    //      fmt.Println("讀取完畢。。")
    //      break
    //  }
    //  fmt.Println(s1)
    //}//4.ReadBytes()
    //data,err :=b1.ReadBytes('\n')
    //fmt.Println(err)
    //fmt.Println(string(data))
​
​
    //Scanner
    //s2 := ""
    //fmt.Scanln(&s2)
    //fmt.Println(s2)
​
    b2 := bufio.NewReader(os.Stdin)
    s2, _ := b2.ReadString('\n')
    fmt.Println(s2)}


本地文件:english.txt文件內容:

寫數據示例代碼:

package main
​
import (
    "os"
    "fmt"
    "bufio"
)func main() {
    /*
    bufio:高效io讀寫
        buffer緩存
        io:input/output
​
    將io包下的Reader,Write對象進行包裝,帶緩存的包裝,提高讀寫的效率
​
        func (b *Writer) Write(p []byte) (nn int, err error)
        func (b *Writer) WriteByte(c byte) error
        func (b *Writer) WriteRune(r rune) (size int, err error)
        func (b *Writer) WriteString(s string) (int, error)
​
     */
​
     fileName := "/Users/ruby/Documents/pro/a/cc.txt"
     file,err := os.OpenFile(fileName,os.O_CREATE|os.O_WRONLY,os.ModePerm)
     if err != nil{
        fmt.Println(err)
        return
     }
     defer file.Close()
​
     w1 := bufio.NewWriter(file)
     //n,err := w1.WriteString("helloworld")
     //fmt.Println(err)
     //fmt.Println(n)
     //w1.Flush() //刷新緩衝區for i:=1;i<=1000;i++{
        w1.WriteString(fmt.Sprintf("%d:hello",i))
     }
     w1.Flush()
}

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