原文鏈接: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()
}