Go Scanner的使用和源碼分析

簡介

go標準庫bufio.Scanner,從字面意思來看是一個掃描器、掃描儀。 所用是不停的從一個reader中讀取數據兵緩存在內存中,還提供了一個注入函數用來自定義分割符。庫中還提供了4個預約義分割方法。數組

  • ScanLines:以換行符分割('n')
  • ScanWords:返回經過「空格」分詞的單詞
  • ScanRunes:返回單個 UTF-8 編碼的 rune 做爲一個 token
  • ScanBytes:返回單個字節做爲一個 token

使用方法

在看使用方法以前,咱們須要先看一個函數。緩存

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

相關文章
相關標籤/搜索