
在go语言中,当程序需要从键盘或通过管道接收输入时,重复创建`bufio.scanner`实例会导致输入数据丢失。这是因为`bufio.scanner`会预读取多行数据到其内部缓冲区。解决此问题的关键在于确保对`os.stdin`的缓冲输入操作始终使用同一个`bufio.scanner`实例,可以通过共享实例或将其封装到自定义类型中,从而有效管理输入流,避免数据遗失。
在Go语言中,bufio.Scanner提供了一种方便高效的方式来逐行读取输入。然而,其内部实现包含一个缓冲机制,这意味着它在读取当前行时,可能会预先读取并存储后续的几行数据到其内部缓冲区中。当我们在程序中每次需要输入时都创建一个新的bufio.Scanner实例来处理os.Stdin时,就会出现问题。
考虑以下示例代码,它尝试通过prompt函数多次获取用户输入:
package main
import (
"bufio"
"fmt"
"os"
)
func print(format string, a ...interface{}) {
fmt.Printf(format+"\n", a...)
}
func prompt(format string) string {
fmt.Print(format)
in := bufio.NewScanner(os.Stdin) // 每次调用都创建新的Scanner
in.Scan()
return in.Text()
}
func greet() {
name := prompt("enter name: ")
print(`Hello %s!`, name)
}
func humor() {
color := prompt("enter favorite color: ")
print(`I like %s too!`, color)
}
func main() {
greet()
humor()
}如果直接运行此程序并通过键盘输入,它会按预期工作。但是,如果我们将一个包含多行数据的文本文件(例如a.txt)通过管道重定向到程序:
a.txt内容:
立即学习“go语言免费学习笔记(深入)”;
bobby bill soft, blue-ish turquoise
执行命令:.\test < a.txt
程序输出将是:
enter name: Hello bobby bill! enter favorite color: I like too!
可以看到,第二行输入soft, blue-ish turquoise被丢失了。这是因为当第一个prompt函数中的bufio.NewScanner(os.Stdin)读取第一行bobby bill时,它可能已经将第二行soft, blue-ish turquoise也读取并缓冲起来。当greet()函数执行完毕,其内部的bufio.Scanner实例被销毁时,这个缓冲的数据也随之被丢弃。随后,humor()函数再次调用prompt()时,又创建了一个全新的bufio.Scanner。由于前一个Scanner已经“消耗”了所有可用的输入(包括第二行),新的Scanner在尝试读取时发现输入流已空,导致返回空字符串。
解决此问题的核心思想是确保对os.Stdin的缓冲输入操作始终使用同一个bufio.Scanner实例。最直接的方法是创建一个包级别的(或全局的)bufio.Scanner实例,并在所有需要读取输入的函数中重用它。
package main
import (
"bufio"
"fmt"
"os"
)
// 创建一个包级别的Scanner实例,只初始化一次
var stdinScanner = bufio.NewScanner(os.Stdin)
func print(format string, a ...interface{}) {
fmt.Printf(format+"\n", a...)
}
func prompt(format string) string {
fmt.Print(format)
stdinScanner.Scan() // 使用共享的Scanner
return stdinScanner.Text()
}
func greet() {
name := prompt("enter name: ")
print(`Hello %s!`, name)
}
func humor() {
color := prompt("enter favorite color: ")
print(`I like %s too!`, color)
}
func main() {
greet()
humor()
}使用这种方式,无论程序调用多少次prompt函数,都只会使用同一个stdinScanner实例。这样,bufio.Scanner的内部缓冲区就能被有效地管理和重用,避免了数据丢失。
注意事项:
为了更好地封装和管理bufio.Scanner实例,可以创建一个自定义类型来持有它,并将相关的输入读取函数作为该类型的方法。这提供了一种更面向对象的方法,避免了全局变量的弊端,并提高了代码的可维护性。
package main
import (
"bufio"
"fmt"
"os"
)
// InputReader 结构体封装了 bufio.Scanner
type InputReader struct {
scanner *bufio.Scanner
}
// NewInputReader 创建并返回一个 InputReader 实例
func NewInputReader() *InputReader {
return &InputReader{
scanner: bufio.NewScanner(os.Stdin),
}
}
// Prompt 方法用于从 InputReader 读取一行输入
func (ir *InputReader) Prompt(format string) string {
fmt.Print(format)
ir.scanner.Scan()
// 在实际应用中,应检查 ir.scanner.Err() 进行错误处理
if err := ir.scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
return "" // 或者根据业务逻辑返回错误
}
return ir.scanner.Text()
}
func print(format string, a ...interface{}) {
fmt.Printf(format+"\n", a...)
}
func main() {
// 创建一个 InputReader 实例
reader := NewInputReader()
// 使用 InputReader 的 Prompt 方法
name := reader.Prompt("enter name: ")
print(`Hello %s!`, name)
color := reader.Prompt("enter favorite color: ")
print(`I like %s too!`, color)
}通过这种方式,bufio.Scanner实例被封装在InputReader类型中,并且Prompt方法作为其成员方法。main函数只需创建一个InputReader实例,然后就可以通过该实例多次调用Prompt方法,确保始终使用同一个bufio.Scanner。这提供了更好的封装性,使得输入管理更加模块化。
优点:
在Go语言中处理标准输入(无论是键盘还是管道文件),尤其是在需要多次读取输入时,务必注意bufio.Scanner的缓冲特性。避免每次读取都创建新的bufio.Scanner实例是解决输入数据丢失的关键。推荐的方法是:
无论选择哪种方法,核心原则都是对os.Stdin保持单一的bufio.Scanner实例,以确保输入流的正确性和完整性。同时,不要忘记在生产代码中加入适当的错误处理,以应对可能出现的I/O错误。
以上就是Go语言中处理标准输入:键盘与管道文件的高效管理的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号