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

知米丶无忌 · · 961 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

介绍

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

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

961 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传