介绍
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函数。
有疑问加站长微信联系(非本文作者)