0

0

Go语言中处理标准输入:避免bufio.Scanner重复创建导致输入丢失

心靈之曲

心靈之曲

发布时间:2025-11-05 14:33:01

|

603人浏览过

|

来源于php中文网

原创

go语言中处理标准输入:避免bufio.scanner重复创建导致输入丢失

本文深入探讨了Go语言中处理标准输入时,使用`bufio.Scanner`可能遇到的一个常见问题:当程序从键盘或重定向文件读取多行输入时,重复创建`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

立即学习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的当前位置开始读取,但此时,第二行数据已经被前一个扫描器读取并丢弃了,导致它无数据可读,最终返回空字符串。

解决方案一:使用全局共享的bufio.Scanner实例

最直接的解决方案是确保所有需要从os.Stdin读取的函数都共享同一个bufio.Scanner实例。这可以通过将bufio.Scanner声明为全局变量来实现。

北极象沉浸式AI翻译
北极象沉浸式AI翻译

免费的北极象沉浸式AI翻译 - 带您走进沉浸式AI的双语对照体验

下载
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实例(推荐)

为了避免全局变量带来的潜在问题,更优雅的解决方案是将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)
}

在这个改进后的代码中:

  1. 我们定义了一个InputReader结构体,其中包含一个*bufio.Scanner字段。
  2. NewInputReader()函数作为构造器,负责创建并初始化InputReader实例。
  3. Prompt()方法现在是InputReader类型的方法,它使用封装在InputReader实例中的scanner来读取输入。
  4. 在main函数中,我们只创建了一个InputReader实例,并将其作为参数传递给greet()和humor()函数。

这种方法的好处在于:

  • 封装性: 将bufio.Scanner及其相关操作封装在一个类型中,提高了代码的模块化。
  • 可维护性: 避免了全局状态,使得代码更容易理解和维护。
  • 可测试性: 方便进行单元测试,可以通过模拟InputReader的行为来测试依赖它的函数。
  • 灵活性: 如果将来需要从其他源(如文件)读取输入,可以轻松扩展InputReader类型或创建新的读取器类型。

总结与最佳实践

当在Go语言中处理多行标准输入(无论是来自键盘还是重定向文件)时,核心原则是:对os.Stdin使用且仅使用一个bufio.Scanner实例。

  • 问题根源: bufio.Scanner会预读并缓冲数据。重复创建实例会导致前一个实例的缓冲区数据丢失。
  • 解决方案:
    1. 全局共享: 将bufio.Scanner声明为全局变量并在init()中初始化。简单但可能引入全局状态问题。
    2. 自定义类型封装(推荐): 创建一个包含bufio.Scanner的自定义结构体,并提供方法来访问它。这种方式提供了更好的封装、可维护性和可测试性。

通过采用自定义类型封装的方法,我们可以编写出更健壮、更易于管理和扩展的Go程序,有效避免因bufio.Scanner的重复创建而导致的输入数据丢失问题。

相关专题

更多
go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

54

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

45

2025.11.27

全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

70

2025.09.18

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

91

2025.09.18

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

246

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

202

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1428

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

606

2023.11.24

笔记本电脑卡反应很慢处理方法汇总
笔记本电脑卡反应很慢处理方法汇总

本专题整合了笔记本电脑卡反应慢解决方法,阅读专题下面的文章了解更多详细内容。

1

2025.12.25

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 2.9万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号