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