
本文深入探讨了Go语言中处理标准输入时,使用`bufio.Scanner`可能遇到的一个常见问题:当程序从键盘或重定向文件读取多行输入时,重复创建`bufio.Scanner`实例会导致后续输入丢失。文章详细分析了问题根源,并提供了两种解决方案:使用全局变量(简单但不推荐)和通过自定义类型封装`bufio.Scanner`实例(推荐的面向对象方法),以确保输入流的正确处理和资源的有效复用。
在Go语言中,bufio.Scanner是处理行分隔输入(如从标准输入或文件)的常用工具。它通过内部缓冲区读取数据,每次调用Scan()方法时,会从缓冲区中提取一行文本。然而,如果程序在每次需要读取输入时都创建一个新的bufio.Scanner实例来处理os.Stdin,尤其是在输入源是重定向的文件时,就会出现输入数据丢失的问题。
考虑以下示例代码,其中prompt函数每次被调用时都会创建一个新的bufio.Scanner:
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()
}当程序通过键盘交互运行时,上述代码通常表现正常。但如果我们将一个包含多行输入的文本文件重定向到程序(例如 .\test < a.txt),其中a.txt内容如下:
立即学习“go语言免费学习笔记(深入)”;
bobby bill soft, blue-ish turquoise
程序输出可能会是这样:
enter name: Hello bobby bill! enter favorite color: I like too!
这与预期不符。问题在于,bufio.Scanner在内部会预读并缓冲多于一行的输入。当greet()函数中的prompt()创建了一个bufio.Scanner并读取了第一行("bobby bill")后,它可能已经将第二行("soft, blue-ish turquoise")甚至更多数据缓冲到了其内部。当prompt()函数返回,这个bufio.Scanner实例被垃圾回收时,其内部缓冲区中尚未读取的数据(即"soft, blue-ish turquoise")也随之丢失了。接着,humor()函数再次调用prompt()时,会创建一个全新的bufio.Scanner。这个新的扫描器会从os.Stdin的当前位置开始读取,但此时,第二行数据已经被前一个扫描器读取并丢弃了,导致它无数据可读,最终返回空字符串。
最直接的解决方案是确保所有需要从os.Stdin读取的函数都共享同一个bufio.Scanner实例。这可以通过将bufio.Scanner声明为全局变量来实现。
package main
import (
"bufio"
"fmt"
"os"
)
var sharedScanner *bufio.Scanner // 声明一个全局的Scanner
func init() {
// 在程序启动时初始化一次
sharedScanner = bufio.NewScanner(os.Stdin)
}
func print(format string, a ...interface{}) {
fmt.Printf(format+"\n", a...)
}
func prompt(format string) string {
fmt.Print(format)
sharedScanner.Scan() // 使用共享的Scanner
return sharedScanner.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()被调用多少次,它都操作同一个bufio.Scanner实例。这样,扫描器内部的缓冲区会持续保存未读取的数据,确保所有行都能被正确处理。
注意事项: 虽然这种方法有效,但在大型或复杂应用中,过度使用全局变量可能导致代码难以维护和测试,因为它引入了全局状态依赖。通常,应优先考虑更具封装性的设计模式。
为了避免全局变量带来的潜在问题,更优雅的解决方案是将bufio.Scanner实例封装在一个自定义类型中,并将其作为该类型的方法来使用。这遵循了面向对象的设计原则,将相关的状态和行为聚合在一起。
我们可以创建一个InputReader类型来持有bufio.Scanner,并提供一个Prompt方法来读取输入。
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 方法用于显示提示并读取一行输入
func (ir *InputReader) Prompt(format string) string {
fmt.Print(format)
ir.scanner.Scan()
return ir.scanner.Text()
}
func print(format string, a ...interface{}) {
fmt.Printf(format+"\n", a...)
}
func greet(reader *InputReader) {
name := reader.Prompt("enter name: ")
print(`Hello %s!`, name)
}
func humor(reader *InputReader) {
color := reader.Prompt("enter favorite color: ")
print(`I like %s too!`, color)
}
func main() {
// 创建一个 InputReader 实例,并在需要时传递给相关函数
reader := NewInputReader()
greet(reader)
humor(reader)
}在这个改进后的代码中:
这种方法的好处在于:
当在Go语言中处理多行标准输入(无论是来自键盘还是重定向文件)时,核心原则是:对os.Stdin使用且仅使用一个bufio.Scanner实例。
通过采用自定义类型封装的方法,我们可以编写出更健壮、更易于管理和扩展的Go程序,有效避免因bufio.Scanner的重复创建而导致的输入数据丢失问题。
以上就是Go语言中处理标准输入:避免bufio.Scanner重复创建导致输入丢失的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号