最近接触到 go 语言相关的 fileserver 项目,夯实基础,以图后效
bufio.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包中Writer类型对 io.Writer 进行了包装,各字段说明:
- err: 用于记录异常
- buf: 用于缓冲的Slice, 默认大小 defaultBufSize = 4096
-
const ( defaultBufSize = 4096 ) // NewWriter returns a new Writer whose buffer has the default size. func NewWriter(w io.Writer) *Writer { return NewWriterSize(w, defaultBufSize) }
-
- n: buf 中已经缓存的字节数,并且提供了一个访问方法
-
// Buffered returns the number of bytes that have been written into the current buffer. func (b *Writer) Buffered() int { return b.n }
// Available returns how many bytes are unused in the buffer.
func (b *Writer) Available() int { return len(b.buf) - b.n }
-
bufio.Writer 的写实现
// 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
// 如果当前 buffer 中没有缓存数据, 则不经过缓存,直接将数据写入文件 if b.Buffered() == 0 { // Large write, empty buffer. // Write directly from p to avoid copy. n, b.err = b.wr.Write(p) } else { // 如果当前 buffer 中 有数据,则将待写数据塞满 buffer 后flush到文件中,进入下一次判断 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 }
写逻辑:
if: 待写数据长度大于缓存可接收长度 { if: 当前 buffer 中没有缓存数据 { 则不经过缓存,直接将数据写入文件 } else: 当前 buffer 中有数据 { 则将待写数据塞满 buffer 后flush到文件中,进入下一次判断 } } else: 待写数据长度 小于 缓存可接收长度 { 则直接写入缓存 }
补充1: io.Writer 功能, io包中Writer方法功能(返回值n 为成功写入的数据,如果 n 小于 待写入数据长度则报error,注意即使报错也有n个字节数据被写入)
// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
Write(p []byte) (n int, err error)
}
补充2: Flush 实现,主要三个功能(将缓存数据写入文件, 写入错误重置缓存, 写入成功缓存索引置零)
// Flush writes any buffered data to the underlying io.Writer. func (b *Writer) Flush() error { if b.err != nil { return b.err } if b.n == 0 { return nil }
// 将缓存数据写到文件 n, err := b.wr.Write(b.buf[0:b.n]) if n < b.n && err == nil { err = io.ErrShortWrite } if err != nil { if n > 0 && n < b.n { // 出现错误时,重置 buffer(删除buffer中错误前已经写入到文件的字节数) copy(b.buf[0:b.n-n], b.buf[n:b.n]) } b.n -= n b.err = err return err }
// 缓存索引置零 b.n = 0 return nil }
至此,bufio Writer 写入数据的实现逻辑分析完毕。
一些思考:
在go的文件 buffer 型写入实现方式中,使用定义slice 作为内存缓存的方式缓冲写入的数据,当待写入数据的长度大于当前 slice 剩余空间(buffer 的 可接收数据长度)或者调用flush 将slice数据写入文件。
可以关注学习操作系统层级的文件缓存,是否有不使用go语言slice作为buf,而使用操作系统提供的 buffer 型写入函数。