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次的數據自己拼接成預期的數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章