go標準庫bufio.Scanner,從字面意思來看是一個掃描器、掃描儀。 所用是不停的從一個reader中讀取數據兵緩存在內存中,還提供了一個注入函數用來自定義分割符。庫中還提供了4個預約義分割方法。數組
在看使用方法以前,咱們須要先看一個函數。緩存
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
這個函數接受一個byte數組,和一個atEOF標誌位(標誌位用來表示是否還有更多的數據)返回的是3個返回值。第一個是推動輸入的字節(通常爲標誌位字節數)
在splist函數判斷是否找到標誌位,若是沒有找到則能夠返回(0,nil,nil) Scan獲取到這個返回值則會繼續讀取以後未讀取完成的字符。若是找到則按照正確的返回值返回。下面是一個簡單的使用例子
func main() { input := "abcend234234234" fmt.Println(strings.Index(input,"end")) scanner := bufio.NewScanner(strings.NewReader(input)) scanner.Split(ScanEnd) //設置讀取緩衝讀取大小 每次讀取2個字節 若是緩衝區不夠則翻倍增長緩衝區大小 buf := make([]byte, 2) scanner.Buffer(buf, bufio.MaxScanTokenSize) for scanner.Scan() { fmt.Println("output:",scanner.Text()) } if scanner.Err() != nil { fmt.Printf("error: %s\n", scanner.Err()) } } func ScanEnd(data []byte, atEOF bool) (advance int, token []byte, err error) { //若是數據爲空,數據已經讀完直接返回 if atEOF && len(data) == 0 { return 0, nil, nil } // 獲取自定義的結束標誌位的位置 index:= strings.Index(string(data),"end") if index > 0{ //若是找到 返回的第一個參數爲後推的字符長度 //第二個參數則指標誌位以前的字符 //第三個參數爲是否有錯誤 return index+3, data[0:index],nil } if atEOF { return len(data), data, nil } //若是沒有找到則返回0,nil,nil return 0, nil, nil }
上面的例子能夠看到 字符串是」abcend234234234「
由於設置的是每次讀取2個字符串
第一次讀取: buf = ab 沒有找到end ScanEnd返回 0,nil,nil
第二次讀取: buf = abce 沒有找到end ScanEnd返回 0,nil,nil
第三次讀取: buf = abcend23(buf翻倍擴容) 找到自定義標誌位end 返回:6,abc, nil 打出 out abc
第四次讀取: buf = 23423423 以前的已經讀取的被去掉,猶豫buf大小爲8 直接讀取8個字符
第五次讀取: 因爲buf容量不足翻倍以後 直接獲取所有數據輸出 out 234234234
結果則是:
output: abc
output: 234234234
能夠看到 掃描器 按照自定義的讀取大小和結束符token 輸出結果函數
type Scanner struct { r io.Reader // reader split SplitFunc // 分割函數 又外部注入 maxTokenSize int // token最大長度 token []byte // split返回的最後一個令牌 buf []byte // 緩衝區字符 start int // buf中的第一個未處理字節 end int // buf中的數據結束 標誌位 err error // Sticky error. empties int // 連續空令牌的計數 scanCalled bool // done bool // 掃描是否完成 } func (s *Scanner) Scan() bool { if s.done { return false } s.scanCalled = true // for循環知道找到token爲止 for { if s.end > s.start || s.err != nil { // 調用split函數 獲得返回值,函數中判斷是否有token token日後推的標誌位數 是否有錯誤 advance, token, err := s.split(s.buf[s.start:s.end], s.err != nil) if err != nil { if err == ErrFinalToken { s.token = token s.done = true return true } s.setErr(err) return false } if !s.advance(advance) { return false } s.token = token if token != nil { if s.err == nil || advance > 0 { s.empties = 0 } else { // Returning tokens not advancing input at EOF. s.empties++ if s.empties > 100 { panic("bufio.Scan: 100 empty tokens without progressing") } } return true } } //若是有錯誤 則返回false if s.err != nil { // Shut it down. s.start = 0 s.end = 0 return false } //從新設置開始位置 和結束位置 讀取更多數據 if s.start > 0 && (s.end == len(s.buf) || s.start > len(s.buf)/2) { copy(s.buf, s.buf[s.start:s.end]) s.end -= s.start s.start = 0 } // 若是buf滿了 若是滿了從新建立一個長度爲原來兩倍大小的buf if s.end == len(s.buf) { const maxInt = int(^uint(0) >> 1) if len(s.buf) >= s.maxTokenSize || len(s.buf) > maxInt/2 { s.setErr(ErrTooLong) return false } newSize := len(s.buf) * 2 if newSize == 0 { newSize = startBufSize } if newSize > s.maxTokenSize { newSize = s.maxTokenSize } newBuf := make([]byte, newSize) copy(newBuf, s.buf[s.start:s.end]) s.buf = newBuf s.end -= s.start s.start = 0 } //若是沒有找到則日後繼續讀取數據 for loop := 0; ; { n, err := s.r.Read(s.buf[s.end:len(s.buf)]) s.end += n if err != nil { s.setErr(err) break } if n > 0 { s.empties = 0 break } loop++ if loop > maxConsecutiveEmptyReads { s.setErr(io.ErrNoProgress) break } } } }
根據上面的源碼和例子能夠看到這個掃描器的做用,固然正式使用時候不會只是讀取一個寫死的字符串。可使用在讀取scoket讀取數據,IO 緩衝區 提供了一個臨時存儲區來存放數據,緩衝區存儲的數據達到必定容量後纔會被"釋放"出來進行下一步存儲,這種方式大大減小了寫操做或是最終的系統調用被觸發的次數,這無疑會在頻繁使用系統資源的時候節省下巨大的系統開銷。而對於讀操做來講,緩衝 IO 意味着每次操做可以讀取更多的數據,既減小了系統調用的次數,又經過以塊爲單位讀取硬盤數據來更高效地使用底層硬件。oop