go bufio


原文链接: go bufio

bufio

bufio是在处理各种 I/O 的时候常用的包之一,他实现了各种带缓冲的 I/O。其中有两个和输入有关的结构:

  • Reader
  • Scanner

下面来看一下这两货怎么玩.

Reader

Readerbufio中提供的一个带缓存的Reader,通过NewReader创建的Reader的默认缓存大小是 4096 byte。也可以通过NewReaderSize来创建一个自定义缓存大小的Reader,不过缓存最小是 16 byte。

来个例子:

func main() {
// Default size.
  reader := bufio.NewReader(os.Stdin)

// Custom size.
  reader = bufio.NewReaderSize(os.Stdin, 1024)

}

上述代码先后创建了两个Reader,都是对stdin进行操作的。其中第一个Reader的缓存大小是 4096 byte,第二个的缓存大小是 1024 byte。

Reader提供了很多很好用的读取函数:

  • Read
  • ReadBytes
  • ReadSlice
  • ReadByte
  • ReadRune
  • ReadString
  • ReadLine

还有一些和输入没太大关系我就忽略了,在函数列表里面看到了ReadLine,不能更激动,终于可以一次读取一行了,不过还是一个一个来讲一下。

Read

func (b *Reader) Read(p []byte) (n int, err error)

Read函数需要一个 byte 切片作为参数,一次读取最多将给定的切片读满。返回值是实际读取的字节数,以及读取过程中遇到的错误。

ReadBytes

func (b *Reader) ReadBytes(delim byte) ([]byte, error)

ReadBytes函数需要给定一个 byte 作为参数,然后他会一直读取,直到读取到给定的 byte,EOF或者在读取过程中发生错误。

从源码里面看到了官方的一行注释 (tǔ cáo) : 哈哈! 还是用Scanner

// For simple uses, a Scanner may be more convenient.

所以没有什么特殊需求的话,还是推荐使用之后讲的Scanner

ReadSlice

func (b *Reader) ReadSlice(delim byte) (line []byte, err error)

ReadSlice函数需要给定一个 byte 作为参数,然后他会一直读取,直到读取到给定的 byte,EOF或者发生错误。

ReadSliceReadBytes的区别在于:ReadBytes有 I/O 操作的,而ReadSlice没有 I/O 操作ReadSlice的读取仅限于已经缓存的部分,而ReadBytes会一直读取输入和刷新缓存。

所以如果某一次调用ReadSlice把缓存读完了的话,后面再调用是不会返回任何结果的,需要调用ReadBytes或者ReadString触发下一次 I/O 操作。所以一般情况下直接调用ReadBytesReadString相对来说好一些。

ReadByte`

func (b *Reader) ReadByte() (byte, error)

ReadByte比较简单粗暴,就是直接读取一个 byte 的内容。

ReadRune

func (b *Reader) ReadRune() (r rune, size int, err error)

Rune是 Go 语言里面的字符类型,可以当做是 C/C++ 里面的char,只不过Rune是 UTF-8 编码的,本质是int32的别名。

ReadRune会读取一个Rune的内容,并且返回这个Rune占用了多少 byte。

ReadString

func (b *Reader) ReadString(delim byte) (string, error)

ReadString函数需要给定一个 byte 作为参数,然后他会一直读取,直到读取到给定的 byte,并将读取的内容转换成字符串返回。

从源码上来看,ReadString直接调用了ReadBytes读取数据,然后转换成了字符串。

[](#ReadLine "ReadLine")ReadLine

func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)

ReadLine会读取一整行的内容,如果这一行的数据很大,超过了缓存的大小,isPrefix会设置为true

从源码上来看,ReadLine是调用了ReadSlice,所以ReadLine是没有 I/O 操作的,并且会丢掉换行符(\n或者\r\n)。

由于是调用了ReadSlice,所以不推荐使用ReadLine来读取一整行内容,而是使用ReadBytes('\n')或者ReadString('\n')来代替。

[](#小结-1 "小结")小结

Reader提供的方法读取到的值都是byterune或者string类型的,用Reader来读取int或者float32等类型的话还不如直接用fmt包的Scan方法。

不过总算是能用Reader一次读取一整行了。

[](#Scanner "Scanner")Scanner

Scanner提供了一些接口,能很方便的一次读取一行,非常适合拿来读取文件之类的数据。Scanner只能通过NewScanner来创建,缓存大小最开始会设置为 4096 byte,之后如果读取到更大的数据会自己调整,最大可以达到 65536 byte。

来个例子:

func main() {

var scanner = bufio.NewScanner(os.Stdin)

}

上述代码创建了一个Scanner,用来对stdin做操作。

Scanner中有一个token的概念,一个token表示一次读取的内容,可以是一整行,也可以是自定义的一段数据,token之间的划分通过SplitFunc来进行,关于SplitFunc的内容包含在了Split函数中了,此处不再赘述。

Scanner提供的方法相对Reader来说要少很多,主要有以下几个:

  • Split
  • Scan
  • Bytes
  • Text

[](#Split "Split")Split

func (s *Scanner) Split(split SplitFunc)

Split 是可以传入一个SplitFunc,传入的这个方法是用来帮忙界定 “行” 的,即你可以自己规定怎么样才算一“行”。默认的SplitFunc就是按行读入。

[](#SplitFunc "SplitFunc")SplitFunc
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)

一个标准的SplitFunc需要接受两个参数:

  • 读取到的原始数据
  • 是否读取到EOF的标识

SplitFunc根据传入的两个参数进行处理,并返回三个值:

  • 本次分割的长度
  • 实际读取到的内容
  • 分割过程中发生的错误

其中第一个返回值不一定就是len(token)Scanner默认的SplitFunc来解释一下:

Scanner默认使用的SplitFunc\n作为分隔符,返回分割的长度,实际读取到的内容和分割过程中的错误,其中分割的长度包含了行尾的\n或者\r\n,而实际读取到的内容中不包含,所以len(token)是不等于advance的。而advance的值,可以看作是偏移量。

其次,还有一个需要注意的地方,如果想通过每次返回advance0的方式来一直读取同一个token的话,会导致程序出现 PANIC。因为在等下要介绍的Scan函数中做了判定:如果连续 100 次读取到的token不为空,但是advance等于 0 的话,会直接 PANIC

panic("bufio.Scan: 100 empty tokens without progressing")

当然,advance为负数的话就更不行了。

[](#Scan-1 "Scan")Scan

func (s *Scanner) Scan() bool

从函数前面上看,Scan非常简单,不接受任何参数,只返回一个布尔值来表示是否已经全部读取完毕。但是从源码中可以知道,Scan做了非常多的事情。

每次调用Scan都会读取一个token,并存放在内部。如果缓存大小不够了会自动扩大缓存。然后还有一些错误的处理等。

[](#Bytes "Bytes")Bytes

func (s *Scanner) Bytes() []byte

Bytes是将上一次调用Scan所读取到的token[]byte的形式返回。

所以连续多次调用Bytes会得到一模一样的结果。

[](#Text "Text")Text

func (s *Scanner) Text() string

TextBytes一样,只不过是将token转化成string再返回。

同样的,连续多次调用Text也会得到一模一样的结果。

[](#小结-2 "小结")小结

Scanner如果能比较好的理解tokenSplitFunc的话,用起来的确比Reader要方便很多。不过和Reader一样,获得的数据都是[]bytestring,如果要获取其他类型的数据的话需要再进行一次转换,不过这时候就不如直接用fmtScan函数来做了。

[](#总结 "总结")总结

为了用 Go 语言做一个 “Hello World” 的大水题我也是够拼的,搞了这么多事情出来。通过这次的折腾,找资料,看源码了解到了许多平常不会注意到的事情:

反正不看源码我是不会知道ReadSliceReadString是一次性的 =。=

最后还是贴一发我的 “Hello world” 的源码:

package main
import (
    "bufio"
    "fmt"
    "os"
)

func main() {

var scan = bufio.NewScanner(os.Stdin)

if scan.Scan() {
    fmt.Printf("Hello, World.\n%s", scan.Text())
  }
}
`