Go 自带的软件包,提供了缓冲 I/O技术,用以优化读取或写入操作。对于写入来说,它在临时存储数据之前进行的(如磁盘或套接字)。数据被存储直到达到特定大小。通过这种方式触发的写操作更少,每个操作都为系统调用,操作会很昂贵。 对于读取而言,这意味着在单次操作中检索更多数据。它还减少了 sycall(系统调用)的数量,但还可以使用更高效的方式使用底层硬件,如读取磁盘块中的数据。本文重点介绍由 bufio 包提供的 Scanner 方法。它对处理数据流很有帮助,方式是将数据拆分为 tokens 并删除它们之间的空间。

"foo bar baz"

如果我们只对字母感兴趣:

package main
import (
    "bufio"
    "fmt"
    "strings"
)
func main() {
    input := "foo   bar      baz"
    scanner := bufio.NewScanner(strings.NewReader(input))
    scanner.Split(bufio.ScanWords)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

输出:

foo
bar
baz
io.Reader
bytes.Splitstrings.Split

实际上(under the hood)扫描器使用缓冲区来累积读取数据。当缓冲区不为空或已达到文件末尾(EOF - End of file)时,将调用分割函数(SplitFunc)。在这之前,我们已经看到了预定义的分割函数,但也可以自己定义:

func(data []byte, atEOF bool) (advance int, token []byte, err error)

Split 函数从数据读取到被调用,基本上可以以 3 种不同的方式运行 - 通过返回的值区分

1.Give me more data

传递的数据不足以获得 token。 它通过返回0,nil,nil 来完成。 发生时,扫描器会尝试读取更多数据。 如果缓冲区已满,则在读取之前将其加倍。让我们看看它是如何工作的:

package main
import (
    "bufio"
    "fmt"
    "strings"
)
func main() {
    input := "abcdefghijkl"
    scanner := bufio.NewScanner(strings.NewReader(input))
    split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        fmt.Printf("%t\t%d\t%s\n", atEOF, len(data), data)
        return 0, nil, nil
    }
    scanner.Split(split)
    buf := make([]byte, 2)
    scanner.Buffer(buf, bufio.MaxScanTokenSize)
    for scanner.Scan() {
        fmt.Printf("%s\n", scanner.Text())
    }
}

输出:

false    2    ab
false    4    abcd
false    8    abcdefgh
false    12    abcdefghijkl
true    12    abcdefghijkl

上面的 split 方法非常简单而且贪婪 – 总是想要更多的数据。Scanner 会试图读取更多,但是当然前提条件是缓冲区需要充足的空间。在我们的例子中空间大小从 2 开始:

buf := make([]byte, 2)
scanner.Buffer(buf, bufio.MaxScanTokenSize)

在第一次调用 split 方法后,Scanner 会将缓冲区的大小加倍,读取更多的数据并第二次调用分割函数。第二次次场景将完全相同。可以从输出中看出来 – 首先调用 split 得到大小为 2 的片段,然后是 4, 8 和最后 12,没有更多的数据了。

缓冲区的默认 size 是 4096

scannerscanner.Split()
package main
import (
    "bufio"
    "errors"
    "fmt"
    "strings"
)
func main() {
    input := "abcdefghijkl"
    scanner := bufio.NewScanner(strings.NewReader(input))
    split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        fmt.Printf("%t\t%d\t%s\n", atEOF, len(data), data)
        if atEOF {
            return 0, nil, errors.New("bad luck")
        }
        return 0, nil, nil
    }
    scanner.Split(split)
    buf := make([]byte, 12)
    scanner.Buffer(buf, bufio.MaxScanTokenSize)
    for scanner.Scan() {
        fmt.Printf("%s\n", scanner.Text())
    }
    if scanner.Err() != nil {
        fmt.Printf("error: %s\n", scanner.Err())
    }
}

输出:

false   12      abcdefghijkl
true    12      abcdefghijkl
error: bad luck

参数 atEOF 还可以用来处理缓冲区里的内容,例如下面这种

foo
bar
baz
\n
package main
import (
    "bufio"
    "fmt"
    "strings"
)
func main() {
    input := "foo\nbar\nbaz"
    scanner := bufio.NewScanner(strings.NewReader(input))
    // Not actually needed since it’s a default split function.
    scanner.Split(bufio.ScanLines)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

2.Token found

这种情况在 split 方法能检测到 token 时发生,It returns the number of characters to move forward in the buffer and the token itself. The reason to return two values is simply because token doesn’t have to be always equal to the number of bytes to move forward. If input is “foo foo foo” and when goal is to detect words (ScanWords), then split function will also skip over spaces in between:

(4, "foo")
(4, "foo")
(3, "foo")
foo
package main
import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "strings"
)
func main() {
    input := "foofoofoo"
    scanner := bufio.NewScanner(strings.NewReader(input))
    split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        if bytes.Equal(data[:3], []byte{'f', 'o', 'o'}) {
            return 3, []byte{'F'}, nil
        }
        if atEOF {
            return 0, nil, io.EOF
        }
        return 0, nil, nil
    }
    scanner.Split(split)
    for scanner.Scan() {
        fmt.Printf("%s\n", scanner.Text())
    }
}

输出:

F
F
F

3.Error

splitscanner
package main
import (
    "bufio"
    "errors"
    "fmt"
    "strings"
)
func main() {
    input := "abcdefghijkl"
    scanner := bufio.NewScanner(strings.NewReader(input))
    split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        return 0, nil, errors.New("bad luck")
    }
    scanner.Split(split)
    for scanner.Scan() {
        fmt.Printf("%s\n", scanner.Text())
    }
    if scanner.Err() != nil {
        fmt.Printf("error: %s\n", scanner.Err())
    }
}

输出:

error: bad luck

有一个特殊的错误不会立即停止 Scanner…

ErrFinalToken

Scanner 提供了一个选项来表示所谓的最终 token。 这是一个特殊的 token,不会中断循环(扫描仍然会返回 true),但随后的扫描将立即停止:

func (s *Scanner) Scan() bool {
    if s.done {
      return false
    }
    ...

输出:

foo
END
io.EOFErrFinalToken

Maximum token size / ErrTooLong

默认情况下,下面使用的缓冲区的最大长度是 64 * 1024 字节。这意味着找到的 token 不能超过此限制:

package main
import (
    "bufio"
    "fmt"
    "strings"
)
func main() {
    input := strings.Repeat("x", bufio.MaxScanTokenSize)
    scanner := bufio.NewScanner(strings.NewReader(input))
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    if scanner.Err() != nil {
        fmt.Println(scanner.Err())
    }
}
bufio.Scanner: token too longBuffer
buf := make([]byte, 10)
input := strings.Repeat("x", 20)
scanner := bufio.NewScanner(strings.NewReader(input))
scanner.Buffer(buf, 20)
for scanner.Scan() {
    fmt.Println(scanner.Text())
}
if scanner.Err() != nil {
    fmt.Println(scanner.Err())
}

输出:

bufio.Scanner: token too long

防止无限循环(Protecting against endless loop)

package main
import (
    "bufio"
    "bytes"
    "fmt"
    "strings"
)
func main() {
    input := "foo|bar"
    scanner := bufio.NewScanner(strings.NewReader(input))
    split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        if i := bytes.IndexByte(data, '|'); i >= 0 {
            return i + 1, data[0:i], nil
        }
        if atEOF {
            return len(data), data[:len(data)], nil
        }
        return 0, nil, nil
    }
    scanner.Split(split)
    for scanner.Scan() {
        if scanner.Text() != "" {
            fmt.Println(scanner.Text())
        }
    }
}

输出:

foo
bar
panic: bufio.Scan: too many empty tokens without progressing

当我第一次阅读 Scanner 或 SplitFunc 的文档时,我的脑海中并不是所有情况下的工作原理都很清楚。看源代码并没有太大的帮助,因为 Scanner 乍一看相当复杂。希望这篇文章能让其他人更清楚。

转自: