go bufio
bufio
bufio
是在处理各种 I/O 的时候常用的包之一,他实现了各种带缓冲的 I/O。其中有两个和输入有关的结构:
Reader
Scanner
下面来看一下这两货怎么玩.
Reader
Reader
是bufio
中提供的一个带缓存的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
或者在读取过程中发生错误。
从源码里面看到了官方的一行注释 : 哈哈! 还是用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
或者发生错误。
ReadSlice
和ReadBytes
的区别在于:ReadBytes
是有 I/O 操作的,而ReadSlice
没有 I/O 操作。ReadSlice
的读取仅限于已经缓存的部分,而ReadBytes
会一直读取输入和刷新缓存。
所以如果某一次调用ReadSlice
把缓存读完了的话,后面再调用是不会返回任何结果的,需要调用ReadBytes
或者ReadString
触发下一次 I/O 操作。所以一般情况下直接调用ReadBytes
和ReadString
相对来说好一些。
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
提供的方法读取到的值都是byte
,rune
或者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
的值,可以看作是偏移量。
其次,还有一个需要注意的地方,如果想通过每次返回advance
为0
的方式来一直读取同一个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
Text
和Bytes
一样,只不过是将token
转化成string
再返回。
同样的,连续多次调用Text
也会得到一模一样的结果。
[](#小结-2 "小结")小结
Scanner
如果能比较好的理解token
和SplitFunc
的话,用起来的确比Reader
要方便很多。不过和Reader
一样,获得的数据都是[]byte
和string
,如果要获取其他类型的数据的话需要再进行一次转换,不过这时候就不如直接用fmt
的Scan
函数来做了。
[](#总结 "总结")总结
为了用 Go 语言做一个 “Hello World” 的大水题我也是够拼的,搞了这么多事情出来。通过这次的折腾,找资料,看源码了解到了许多平常不会注意到的事情:
反正不看源码我是不会知道ReadSlice
和ReadString
是一次性的 =。=
最后还是贴一发我的 “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())
}
}