bufio包 - golang

     之前提到了和硬件打交道用到了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次的数据自己拼接成预期的数据。

你可能感兴趣的:(golang)