Go語言入門【五】:源碼學習-bufio

介紹

package bufio也是io的一部分,但在不同包中,因此獨立一節(jié)。
其中包含bufio.go,scan.go兩部分。

bufio

bufio的作用是為一個已有的Reader或者Writer提供緩沖,我們知道操作系統(tǒng)的io是資源瓶頸,應該盡可能少的調(diào)用io操作,所以把大批量的數(shù)據(jù)一起讀取或?qū)懭胧歉玫倪x擇。使用方法:

    w := bufio.NewWriter(os.Stdout)
    fmt.Fprint(w, "Hello, ")
    fmt.Fprint(w, "world!")
    w.Flush() // Don't forget to flush!
    // Output: Hello, world!

源碼中對Reader和Writer做了一個簡單封裝,bufio.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
    lastRuneSize int
}

除了包括原始的reader,還有一個[]byte結(jié)構(gòu),過程以Read方法為例:

// 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).
// 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()
        }
        b.fill() // buffer is empty
        if b.r == b.w {
            return 0, b.readErr()
        }
    }

    // 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
}

解釋:每次讀取只調(diào)用內(nèi)部reader的一次操作,
如果內(nèi)部的buf小于提供的p,那么直接讀取到p里,不經(jīng)過buf。
如果buf更大,做一次fill操作:1.清理buf中的遺留數(shù)據(jù)到buf頭部,2.讀取內(nèi)部reader到buf,并向后移動w,w+=n
最后做了一次copy操作,將buf的內(nèi)容copy到p中。

再以Writer.Write為例:

// 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
}

解釋:
n指buf中已經(jīng)寫了多少字節(jié)
b.Available()指buf中還剩多少字節(jié)可寫,等于len(buf)-n
b.Buffered()就是n
來看過程:
首先如果n=0,那么直接把p寫入到內(nèi)部writer
如果buf中有東西,那么把p的內(nèi)容先copy到buf中,并做一次flush(即buf寫入writer)
只要buf中沒有足夠的空間(小于len(p)),都會持續(xù)的寫入writer。
最后一點點尾巴,只能暫時留在buf里,等待下一次flush操作了。

在使用的場景中來看bufio.Writer的用途:
小buf,大量的寫入數(shù)據(jù):這樣就類似于不加這個buf,只留一點點尾巴。
大buf,小數(shù)據(jù)寫入:這樣就有可能不寫入,只是把數(shù)據(jù)先放到buf里。

Scanner

Scanner的作用是對一個Reader進行迭代,使用方式如下:

scanner := bufio.NewScanner(reader)
    for scanner.Scan() {
        fmt.Println(scanner.Text()) // Println will add back the final '\n'
    }

默認按照一行一行進行讀取,每次scan,scanner.Text()都會返回下一行的數(shù)據(jù),直到EOF,Scan()返回false。
我們來看源碼,主要是Scanner結(jié)構(gòu):

type Scanner struct {
    r            io.Reader // The reader provided by the client.
    split        SplitFunc // The function to split the tokens.
    maxTokenSize int       // Maximum size of a token; modified by tests.
    token        []byte    // Last token returned by split.
    buf          []byte    // Buffer used as argument to split.
    start        int       // First non-processed byte in buf.
    end          int       // End of data in buf.
    err          error     // Sticky error.
    empties      int       // Count of successive empty tokens.
    scanCalled   bool      // Scan has been called; buffer is in use.
    done         bool      // Scan has finished.
}

每次返回的『一行』(其實未必是一行,暫且這么叫)稱為token,移動到下一個token稱為一次advance,通過split函數(shù)做tokenize。其他都是一些比較明顯的輔助字段。

這里主要是這個split函數(shù),默認的bufio.NewScanner()代碼如下:

func NewScanner(r io.Reader) *Scanner {
    return &Scanner{
        r:            r,
        split:        ScanLines,
        maxTokenSize: MaxScanTokenSize,
    }
}

以分行函數(shù)作為split,同時看到MaxScanTokenSize = 64 * 1024,也就是說一行不能太長。否則會拋錯,除非使用scanner.Buffer()方法自己提供緩沖區(qū)和最大容量。

除了默認的ScanLines,系統(tǒng)還提供了ScanRunes,ScanWords,ScanBytes三個split函數(shù),用戶也可以自定義split函數(shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容