之前提到了和硬件打交道用到了dial,简单的学习了一下,知道了如何收发数据。但是当我们拿到数据后如何处理呢。对于一般的来说,数据比较简单,我们一般直接解析就行。但是有的你会拿到一个打包的文件流,然后需要你进行解析。在之前的使用中,就遇到一个比较复杂的数据流,需要分块解析。在那次中用到了go的bufio。
先让我们看一下下面的这个代码:
func main() {
f,e := os.Open("文章.txt")
buf_len, _ := f.Seek(0, io.SeekEnd) //获得游标长度
fmt.Println("文件大小:",buf_len)
f.Seek(0, io.SeekStart) //把游标放到初始位置
if e != nil {
fmt.Println(e)
return
}
rd := bufio.NewReader(f)
b := make([]byte,3000)
n,e := rd.Read(b)
fmt.Println("第一次读取:",n)
b2 := make([]byte,3000)
n,e = rd.Read(b2)
fmt.Println("第二次读取:",n)
b3 := make([]byte,3000)
n,e = rd.Read(b3)
fmt.Println("第三次读取:",n)
}
输出结果:
文件大小: 7954
第一次读取: 3000
第二次读取: 1096
第三次读取: 3000
在使用read的时候,我们拿到了2个返回值,一个是读取的大小,一个是返回的错误。但是通过2次对比我们发现,文件7954大小,按理说读取2次都应该是3000才对,但是我们看到只读出了1096字节。
如果你跟进源码就会发现这两个:
// Package bufio implements buffered I/O. It wraps an io.Reader or io.Writer
// object, creating another object (Reader or Writer) that also implements
// the interface but provides buffering and some help for textual I/O.
const (
defaultBufSize = 4096
)
func NewReader(rd io.Reader) *Reader {
return NewReaderSize(rd, defaultBufSize)
}
大体就是bufio是一个带缓存io,他实现了io.Reader or io.Writer 。同时bufio的默认缓存是4096个字节。然后让我们看一下对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).
// To read exactly len(p) bytes, use io.ReadFull(b, 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()
}
// One read.
// Do not use b.fill, which will loop.
b.r = 0
b.w = 0
n, b.err = b.rd.Read(b.buf)
if n < 0 {
panic(errNegativeRead)
}
if n == 0 {
return 0, b.readErr()
}
b.w += n
}
// 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
}
这里提到,读取n的长度是可能小于len(p)的长度的。这是因为bufio自带缓存,在缓存剩余大小不为0的情况下,只会读取缓存中的数据。这就导致了上面第二次读取只读取了1096字节的情况。同时他也说明了如果要精确获得p的数据,建议使用io.readfull()。
同时,如果缓存剩余数据为0时,bufio的read就会再次把默认缓存大小的字节加载进缓存中,这也就解释了,为什么第三次读取为什么又是3000字节的问题。
然后让我们根据提示修改上面的代码,就可以看到:
func main() {
f,e := os.Open("文章.txt")
buf_len, _ := f.Seek(0, io.SeekEnd) //获得游标长度
fmt.Println("文件大小:",buf_len)
f.Seek(0, io.SeekStart) //把游标放到初始位置
if e != nil {
fmt.Println(e)
return
}
rd := bufio.NewReader(f)
b := make([]byte,3000)
n,e := rd.Read(b)
fmt.Println("第一次读取:",n)
b2 := make([]byte,3000)
n,e = io.ReadFull(rd,b2)
fmt.Println("第二次读取:",n)
b3 := make([]byte,3000)
n,e = rd.Read(b3)
fmt.Println("第三次读取:",n)
}
读取结果:
文件大小: 7954
第一次读取: 3000
第二次读取: 3000
第三次读取: 1954
这次结果就和我们预期的一样了,同时我们通过最开始的代码也会发现NewReaderSize是一个公开的方法。所以我们也可以利用这个方法修改成文件大小的缓存,这样也可以解决读不全的问题。当然这样文件太大就不太适用了。
让我们终结一下,如果用到了bufio,同时想在读取过程中拿到和预期一样大小数据的话,我们可以利用以下几点:
1. 直接用NewReaderSize()设置初始缓存的数据大小,然后直接用read方法读取。
2. 通过io.readfull()方法读取。
3. 同时我们通过第二次和第三次发现,一次去了剩余缓存大小,一次取了底层数据。我们也可以利用这点自己写逻辑,把2次的数据自己拼接成预期的数据。