Go语言入门【五】:源码学习-bufio

作者: 纳达丶无忌 | 来源:发表于2016-12-01 10:46 被阅读675次

    介绍

    package bufio也是io的一部分,但在不同包中,因此独立一节。
    其中包含bufio.go,scan.go两部分。

    bufio

    bufio的作用是为一个已有的Reader或者Writer提供缓冲,我们知道操作系统的io是资源瓶颈,应该尽可能少的调用io操作,所以把大批量的数据一起读取或写入是更好的选择。使用方法:

        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结构,过程以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
    }
    

    解释:每次读取只调用内部reader的一次操作,
    如果内部的buf小于提供的p,那么直接读取到p里,不经过buf。
    如果buf更大,做一次fill操作:1.清理buf中的遗留数据到buf头部,2.读取内部reader到buf,并向后移动w,w+=n
    最后做了一次copy操作,将buf的内容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中已经写了多少字节
    b.Available()指buf中还剩多少字节可写,等于len(buf)-n
    b.Buffered()就是n
    来看过程:
    首先如果n=0,那么直接把p写入到内部writer
    如果buf中有东西,那么把p的内容先copy到buf中,并做一次flush(即buf写入writer)
    只要buf中没有足够的空间(小于len(p)),都会持续的写入writer。
    最后一点点尾巴,只能暂时留在buf里,等待下一次flush操作了。

    在使用的场景中来看bufio.Writer的用途:
    小buf,大量的写入数据:这样就类似于不加这个buf,只留一点点尾巴。
    大buf,小数据写入:这样就有可能不写入,只是把数据先放到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()都会返回下一行的数据,直到EOF,Scan()返回false。
    我们来看源码,主要是Scanner结构:

    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函数做tokenize。其他都是一些比较明显的辅助字段。

    这里主要是这个split函数,默认的bufio.NewScanner()代码如下:

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

    以分行函数作为split,同时看到MaxScanTokenSize = 64 * 1024,也就是说一行不能太长。否则会抛错,除非使用scanner.Buffer()方法自己提供缓冲区和最大容量。

    除了默认的ScanLines,系统还提供了ScanRunes,ScanWords,ScanBytes三个split函数,用户也可以自定义split函数。

    相关文章

      网友评论

        本文标题:Go语言入门【五】:源码学习-bufio

        本文链接:https://www.haomeiwen.com/subject/ehjxmttx.html