前言
本文翻译自 How to read and write with Golang bufio。
在 Go 语言中,bufio
是一个用于缓冲 IO 的包。缓冲 IO 是一种用于在转发之前临时累积 IO 操作结果的技术。这项技术可以通过减少系统调用的数量来提升程序的速度,系统调用通常是缓慢的操作。在这篇文章中,我们将看看 bufio
为写入和读写操作提供的一些抽象。
使用 bufio 写入
通过 bufio
, 我们可以使用 bufio.Writer
方法在写入 IO 之前累计到缓冲区中。在下面的示例中,我们展示了你可能遇到的三种情况:
- 缓冲区已满
- 写入后缓冲区有空间
- 写入大于缓冲区容量
1. 缓冲区已满
一旦缓冲区满了,就执行写操作。
2. 写入后缓冲区有空间
如果缓冲区在最后一次写入后仍有空间,它将不会尝试完成写入,直到 Flush()
方法明确要求这样做。
3. 写入大于缓冲区容量
如果写入大于缓冲区容量,缓冲区将会被跳过,因为已经不需要缓冲了。
package main
import (
"fmt"
"bufio"
)
// 使用 Writer 类型初始化缓冲 writer
type Writer int
func (*Writer) Write(p []byte) (n int, err error) {
fmt.Printf("Writing: %s\n",p)
return len(p), nil
}
func main() {
// 声明一个缓冲 writer
// 缓冲大小为4字节
w := new(Writer)
bw := bufio.NewWriterSize(w, 4)
// Case 1: 写满缓冲区
bw.Write([]byte{'1'})
bw.Write([]byte{'2'})
bw.Write([]byte{'3'})
bw.Write([]byte{'4'}) // write - 缓冲区满
// Case 2: 缓冲仍有空间
bw.Write([]byte{'5'})
err := bw.Flush() // 强制写入剩下的
if err != nil {
panic(err)
}
// Case 3: 写入超过缓冲区容量
// 将会跳过缓冲直接写入
bw.Write([]byte("12345"))
}
其他写入功能
重用
我们可以使用 reset()
方法为不同的 writer 重用相同的 bufio.NewWriterSize
:
writerOne := new(Writer)
bw := bufio.NewWriterSize(writerOne,2)
writerTwo := new(Writer)
bw.Reset(writerTwo)
检查可用空间
我们可以使用 Available()
方法检查可用空间。
使用 bufio 读取
bufio
允许我们使用 bufio.Reader 批量读取。 读取后,根据需要将数据从缓冲区释放到消费者。 在下面的示例中,我们将看到:
- Peek
- ReadSlice
- ReadLine
- ReadByte
- Scanner
1. Peek
Peek(窥视) 方法让我们可以查看缓冲区中的前 n 个字节(称为 peek 值),而无需消耗它们。 该方法以下列方式操作。
- 如果 peek 值小于缓冲区容量,则返回等于 peek 值的字符。
- 如果 peek 值大于缓冲区容量,则返回 bufio.ErrBufferFull。
- 如果 peek 值包含 EOF 并且小于缓冲区容量,则返回 EOF。
2. ReadSlice
ReadSlice 方法签名:
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
它返回包含分隔符的字符串切片。例如,如果输入为 1,2,3 并且我们将逗号作为分隔符,将会输出:
1,
2,
3
如果找不到分隔符,并且已达到 EOF,则返回 io.EOF。 如果未达到分隔符且 readSlice
已超出缓冲区容量,则返回 io.ErrBufferFull。
3. ReadLine
ReadLine 定义如下:
ReadLine() (line []byte, isPrefix bool, err error)
ReadLine
内部使用 ReadSlice
。 但是,它会从返回的切片中删除换行符(\n
或 \r\n
)。
请注意,它的签名是不同的,因为它也返回
isPrefix
标志。 当未找到分隔符且内部缓冲区已满时,此标志返回 true。
Readline
不处理长于内部缓冲区的行。 我们可以多次调用它来完成读取。
4. ReadByte
ReadByte 签名如下:
func (b *Reader) ReadBytes(delim byte) ([]byte, error)
与 ReadSlice 类似,ReadBytes 返回在分隔符之前(包括分隔符)的切片。 事实上,ReadByte 在 ReadSlice 上工作,它充当底层的低级函数。 但是,ReadByte 可以调用多个 ReadSlice 实例来累积返回数据; 因此,绕过缓冲区大小限制。 此外,由于 ReadByte 返回一个新的字节切片,因此使用起来更安全,因为随后的读取操作不会覆盖数据。
5. Scanner
Scanner(扫描) 通过将数据流拆分为 token 来中断数据流。 扫描在 EOF、第一次 IO 错误或令牌太大而无法放入缓冲区时停止。 如果需要对错误处理进行更多控制,请使用 bufio.Reader。Scanner 具有以下签名:
func NewScanner(r io.Reader) *Scanner
这是用于将文本划分为标记的 split 函数,默认为 ScanLines; 但是,如果需要你可以更改它。
package main
import (
"bufio"
"fmt"
"strconv"
"strings"
)
const singleLine string = "I'd love to have some coffee right about now"
const multiLine string = "Reading is my...\r\n favourite"
func main() {
fmt.Println("Lenght of singleLine input is " + strconv.Itoa(len(singleLine)))
str := strings.NewReader(singleLine)
br := bufio.NewReaderSize(str, 25)
fmt.Println("\n---Peek---")
// Peek - Case 1: 简单 peek 实现
b, err := br.Peek(3)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%q\n",b) // 输出: "I'd"
// Peek - Case 2: Peek 大于缓存大小
b, err = br.Peek(30)
if err != nil {
fmt.Println(err) // 输出: "bufio: buffer full"
}
// Peek - Case 3: 缓冲大小大于字符串
br_large := bufio.NewReaderSize(str,50)
b, err = br_large.Peek(50)
if err != nil {
fmt.Println(err) // 输出: EOF
}
// ReadSlice
fmt.Println("\n---ReadSlice---")
str = strings.NewReader(multiLine)
r := bufio.NewReader(str)
for {
token, err := r.ReadSlice('.')
if len(token) > 0 {
fmt.Printf("Token (ReadSlice): %q\n", token)
}
if err != nil {
break
}
}
// ReadLine
fmt.Println("\n---ReadLine---")
str = strings.NewReader(multiLine)
r = bufio.NewReader(str)
for {
token, _ , err := r.ReadLine()
if len(token) > 0 {
fmt.Printf("Token (ReadLine): %q\n", token)
}
if err != nil {
break
}
}
// ReadBytes
fmt.Println("\n---ReadBytes---")
str = strings.NewReader(multiLine)
r.Reset(str)
for {
token, err := r.ReadBytes('\n')
fmt.Printf("Token (ReadBytes): %q\n", token)
if err != nil {
break
}
}
// Scanner
fmt.Println("\n---Scanner---")
str = strings.NewReader(multiLine)
scanner := bufio.NewScanner(str)
for scanner.Scan() {
fmt.Printf("Token (Scanner): %q\n", scanner.Text())
}
}