Go输入与输出格式化案例详解

Written by

in

文章目录
  • Go语言中,输入与输出(I/O)操作主要通过 fmt 包和 bufio 包实现。fmt 包提供了基本的格式化输入输出功能,而 bufio 包则用于更高效的缓冲读写操作。
  • bufio 是 “buffered I/O” 的缩写,它通过在 I/O 操作中引入缓冲区来提升性能。这个包包装了 io.Reader 和 io.Writer 对象,创建了新的、带缓冲的 Reader 和 Writer 实例,提供了额外的功能。
  • 目录
    • 一、Go语言输入与输出概述
    • 二、输出格式化详解
      • 2.1 常用输出函数
      • 2.2 格式化动词(Verbs)
      • 2.3 案例
    • 三、输入格式化详解
      • 3.1 常用输入函数
      • 3.2 案例
    • 四、Go语言bufio详解
      • 4.1 为什么需要bufio?(核心思想)
      • 4.2bufio.Reader:带缓冲的读取器
      • 4.3bufio.Writer:带缓冲的写入器
      • 4.4bufio.Scanner:更高级的读取工具
      • 4.5 使用对比

    Go语言中,输入与输出(I/O)操作主要通过 fmt 包和 bufio 包实现。fmt 包提供了基本的格式化输入输出功能,而 bufio 包则用于更高效的缓冲读写操作。

    Go语言中,fmt 包提供了以下常用输出函数:

    • fmt.Print(a ...interface{}):打印参数,不添加换行。
    • fmt.Println(a ...interface{}):打印参数,自动添加空格和换行。
    • fmt.Printf(format string, a ...interface{}):按格式化字符串打印。
    • fmt.Sprintf(format string, a ...interface{}):返回格式化后的字符串,不打印。
    • fmt.Fprintf(w io.Writer, format string, a ...interface{}):将格式化内容写入 io.Writer

    格式化动词用于控制输出格式,常用动词包括:

    动词 说明
    %v 值的默认格式
    %+v 类似 %v,但输出结构体时会添加字段名
    %#v 值的 Go 语法表示
    %T 值的类型
    %% 百分号
    %d 十进制整数
    %b 二进制整数
    %x 十六进制整数(小写)
    %f 浮点数
    %s 字符串
    %p 指针地址

    package main
    import (
    	"fmt"
    	"os"
    )
    type Person struct {
    	Name string
    	Age  int
    }
    func main() {
    	// 基本输出
    	fmt.Print("Hello, ")
    	fmt.Println("World!")
    	// 格式化输出
    	name := "Alice"
    	age := 25
    	fmt.Printf("Name: %s, Age: %dn", name, age)
    	// 结构体格式化
    	person := Person{Name: "Bob", Age: 30}
    	fmt.Printf("Person: %vn", person)         // 输出: Person: {Bob 30}
    	fmt.Printf("Person: %+vn", person)        // 输出: Person: {Name:Bob Age:30}
    	fmt.Printf("Person: %#vn", person)        // 输出: Person: main.Person{Name:"Bob", Age:30}
    	// 返回格式化字符串
    	formattedStr := fmt.Sprintf("Name: %s, Age: %d", name, age)
    	fmt.Println("Formatted string:", formattedStr)
    	// 写入文件
    	file, err := os.Create("output.txt")
    	if err != nil {
    		fmt.Println("Error creating file:", err)
    		return
    	}
    	defer file.Close()
    	fmt.Fprintf(file, "This is written to a file. Name: %s, Age: %dn", name, age)
    	fmt.Println("Data written to file.")
    }

    Go语言中,fmt 包提供了以下常用输入函数:

    • fmt.Scan(a ...interface{}):从标准输入读取数据,按空格分隔。
    • fmt.Scanln(a ...interface{}):类似 Scan,但读取到换行符时停止。
    • fmt.Scanf(format string, a ...interface{}):按格式化字符串读取输入。

    package main
    import (
    	"bufio"
    	"fmt"
    	"os"
    	"strings"
    )
    func main() {
    	// 使用 fmt.Scan 读取输入
    	var name string
    	var age int
    	fmt.Print("Enter your name and age (separated by space): ")
    	fmt.Scan(&name, &age)
    	fmt.Printf("Name: %s, Age: %dn", name, age)
    	// 使用 fmt.Scanln 读取输入
    	fmt.Print("Enter your name: ")
    	fmt.Scanln(&name)
    	fmt.Printf("Name: %sn", name)
    	// 使用 fmt.Scanf 读取格式化输入
    	fmt.Print("Enter your name and age (format: Name Age): ")
    	fmt.Scanf("%s %d", &name, &age)
    	fmt.Printf("Name: %s, Age: %dn", name, age)
    	// 使用 bufio 读取整行输入
    	reader := bufio.NewReader(os.Stdin)
    	fmt.Print("Enter a sentence: ")
    	sentence, _ := reader.ReadString('n')
    	sentence = strings.TrimSpace(sentence)
    	fmt.Printf("You entered: %sn", sentence)
    }

    代码说明

    1. 输出部分
      • PrintPrintln 用于简单输出,后者会自动换行。
      • Printf 支持格式化动词,可以灵活控制输出格式。
      • Sprintf 返回格式化后的字符串,不直接打印。
      • Fprintf 将格式化内容写入 io.Writer,如文件。
    2. 输入部分
      • ScanScanln 用于读取标准输入,按空格或换行符分隔。
      • Scanf 支持格式化输入,适用于特定格式的数据读取。
      • bufio.NewReader 提供更高效的缓冲读取功能,适合读取整行输入。

    bufio 是 “buffered I/O” 的缩写,它通过在 I/O 操作中引入缓冲区来提升性能。这个包包装了 io.Readerio.Writer 对象,创建了新的、带缓冲的 ReaderWriter 实例,提供了额外的功能。

    想象一下没有缓冲区的 I/O 操作,就像用一个非常小的勺子(比如一次只能装一个字节)从一个大水桶(文件或网络连接)里取水。每次取水都需要一个完整的“伸手-舀水-收回”的动作,这个过程的开销很大。
    bufio 的作用就是引入一个“水杯”(缓冲区)。现在你的操作变成了:

    1. 用勺子从水桶里快速舀满一杯水(一次性读取一大块数据到缓冲区)。
    2. 需要用水时,直接从水杯里倒(从缓冲区读取数据给程序)。
      这个过程大大减少了“伸手-舀水-收回”的次数,从而显著提高了 I/O 效率,尤其是在处理大量小数据块或网络通信时。

    核心优势:

    • 减少系统调用:将多次小的读/写操作合并为少数几次大的操作,减少了昂贵的系统调用次数。
    • 提供高级功能:除了性能提升,bufio 还提供了许多便捷的方法,如按行读取、按单词读取等。

    bufio.Reader 是一个结构体,它包装了一个 io.Reader 接口。

    1、创建 bufio.Reader:使用 bufio.NewReader 函数创建

    func NewReader(rd io.Reader) *Reader

    示例: 从标准输入创建一个缓冲读取器。

    package main
    import (
    	"bufio"
    	"fmt"
    	"os"
    )
    func main() {
    	// os.Stdin 实现了 io.Reader 接口
    	reader := bufio.NewReader(os.Stdin)
    	fmt.Printf("创建了一个缓冲区大小为 %d 字节的 Readern", reader.Size())
    }

    2、核心方法bufio.Reader 提供了一系列强大的读取方法。

    a) ReadString(delim byte) – 按分隔符读取
    这是最常用的方法之一。它会读取数据,直到遇到指定的分隔符 delim,然后返回包含分隔符在内的字符串。

    func (b *Reader) ReadString(delim byte) (string, error)

    示例: 逐行读取用户输入,直到用户输入 “exit”。

    package main
    import (
    	"bufio"
    	"fmt"
    	"os"
    	"strings"
    )
    func main() {
    	reader := bufio.NewReader(os.Stdin)
    	fmt.Println("请输入内容(输入 'exit' 退出):")
    	for {
    		fmt.Print("> ")
    		// 读取直到遇到换行符 'n'
    		input, err := reader.ReadString('n')
    		if err != nil {
    			fmt.Println("读取错误:", err)
    			return
    		}
    		// 去除字符串两端的空白字符(包括换行符)
    		input = strings.TrimSpace(input)
    		if input == "exit" {
    			fmt.Println("程序退出。")
    			break
    		}
    		fmt.Println("你输入了:", input)
    	}
    }

    b) ReadBytes(delim byte) – 按分隔符读取字节切片
    ReadString 类似,但返回的是 []byte 类型,在处理二进制数据或需要更高性能时非常有用。

    func (b *Reader) ReadBytes(delim byte) ([]byte, error)

    c) ReadLine() – 读取一行(不包含行尾符)
    这是一个底层方法,它会读取一行,但不包含行尾的 rn。它返回三个值:line (内容), isPrefix (是否过长), err (错误)。通常我们更常用 ReadString('n'),因为它更简单直观。

    d) ReadSlice(delim byte) – 读取到分隔符的切片

    这个方法返回的是对缓冲区内部数据的引用,而不是拷贝。这意味着下一次读取操作可能会覆盖这次返回的数据。它性能更高,但使用起来需要更小心。

    e) Peek(n int) – 预览数据
    Peek 返回缓冲区接下来的 n 个字节,但不会移动读取指针。也就是说,下次调用 Read 或其他读取方法时,仍然会从这次 Peek 的位置开始读。非常适合用于“偷看”一下接下来的内容是什么。

    func (b *Reader) Peek(n int) ([]byte, error)

    f) Read(p []byte) – 实现 io.Reader 接口
    bufio.Reader 本身也实现了 io.Reader 接口。当你调用它的 Read 方法时,它会尝试从自己的缓冲区中填满你提供的字节切片 p。如果缓冲区空了,它会从底层的 io.Reader 中读取数据来填满缓冲区,然后再满足你的请求。

    bufio.Writer 包装了一个 io.Writer 接口,将写入操作先存入内存缓冲区,当缓冲区满了或被显式刷新时,才一次性写入底层的 io.Writer

    创建 bufio.Writer:使用 bufio.NewWriter 函数创建:

    func NewWriter(w io.Writer) *Writer

    示例: 创建一个写入标准输出的缓冲写入器。

    package main
    import (
    	"bufio"
    	"fmt"
    	"os"
    )
    func main() {
    	// os.Stdout 实现了 io.Writer 接口
    	writer := bufio.NewWriter(os.Stdout)
    	fmt.Printf("创建了一个缓冲区大小为 %d 字节的 Writern", writer.Size())
    }

    核心方法
    a) Write(p []byte) – 写入字节切片
    这是最核心的写入方法。它将数据写入缓冲区。如果缓冲区有足够空间,数据就暂存于此;如果空间不足,它会先将缓冲区的内容刷到底层 Writer,然后再写入新数据。
    b) WriteString(s string) – 写入字符串
    WriteString 是一个便捷方法,内部也是调用 Write
    c) Flush() – 强制刷新
    这是 bufio.Writer 最重要的方法。它会将缓冲区中所有(无论是否已满)的数据都写入到底层的 io.Writer 中。

    为什么 Flush 如此重要?
    因为如果不调用 Flush(),当程序结束时,缓冲区里可能还有未写入的数据,这些数据就会丢失!

    示例: 向文件写入多行内容。

    package main
    import (
    	"bufio"
    	"fmt"
    	"log"
    	"os"
    )
    func main() {
    	// 1. 创建文件
    	file, err := os.Create("output.txt")
    	if err != nil {
    		log.Fatal(err)
    	}
    	// 确保文件在函数结束时关闭
    	defer file.Close()
    	// 2. 创建缓冲写入器
    	writer := bufio.NewWriter(file)
    	// 3. 写入数据(此时数据在内存缓冲区中)
    	fmt.Fprintln(writer, "这是第一行。")
    	fmt.Fprintln(writer, "这是第二行。")
    	writer.WriteString("这是第三行,使用 WriteString。n")
    	// 4. **必须刷新缓冲区,将数据写入文件**
    	err = writer.Flush()
    	if err != nil {
    		log.Fatal("刷新缓冲区失败:", err)
    	}
    	fmt.Println("数据已成功写入 output.txt")
    }

    在这个例子中,如果注释掉 writer.Flush()output.txt 文件将是空的。

    从 Go 1.1 开始,bufio 包引入了 Scanner 类型,它提供了一个更简单、更健壮的方式来读取数据,特别是按“token”(标记)分割数据。核心思想:
    Scanner 的核心是 SplitFunc 类型的函数,它定义了如何将输入流分割成一个个标记。默认的分割函数是 ScanLines,即按行分割。

    使用步骤

    1. 创建 Scanner: bufio.NewScanner(r io.Reader)
    2. (可选)设置分割函数: scanner.Split(customSplitFunc)
    3. 循环扫描: 使用 scanner.Scan() 作为循环条件,它会尝试读取下一个标记。
    4. 获取标记: 在循环体内,使用 scanner.Text() (返回 string) 或 scanner.Bytes() (返回 []byte) 获取当前标记的内容。
    5. 检查错误: 循环结束后,使用 scanner.Err() 检查是否发生了错误。

    案例1:逐行读取文件
    使用 Scanner 逐行读取文件比 Reader.ReadString 更简洁,且能更好地处理行尾符。

    package main
    import (
    	"bufio"
    	"fmt"
    	"log"
    	"os"
    	"strings"
    )
    func main() {
    	// 1. 打开文件
    	file, err := os.Open("my_file.txt")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer file.Close()
    	// 2. 创建 Scanner
    	scanner := bufio.NewScanner(file)
    	lineCount := 0
    	wordCount := 0
    	// 3. 循环扫描
    	for scanner.Scan() {
    		line := scanner.Text()
    		lineCount++
    		// 简单地按空格分割来计算单词数
    		words := strings.Fields(line)
    		wordCount += len(words)
    		fmt.Printf("行 %d: %sn", lineCount, line)
    	}
    	// 4. 检查错误
    	if err := scanner.Err(); err != nil {
    		log.Fatal("扫描时出错:", err)
    	}
    	fmt.Printf("n扫描完成。共 %d 行,%d 个单词。n", lineCount, wordCount)
    }

    案例2:自定义分割函数
    Scanner 的强大之处在于可以自定义分割逻辑。例如,我们可以按单词读取。
    示例: 使用 ScanWords 分割函数按单词读取。

    // ... (前面的代码相同)
    scanner := bufio.NewScanner(strings.NewReader("Hello world, this is a test."))
    // 设置分割函数为按单词分割
    scanner.Split(bufio.ScanWords)
    for scanner.Scan() {
    	fmt.Println(scanner.Text())
    }
    if err := scanner.Err(); err != nil {
        // ...
    }

    输出:

    Hello
    world,
    this
    is
    a
    test.

    类型 适用场景 优点 注意事项
    bufio.Reader 需要精细控制读取过程,如读取固定字节数、预览数据、读取到特定分隔符。 功能强大,控制力强,性能高。 API 相对底层,使用时需注意错误处理。
    bufio.Writer 需要频繁进行小块写入,以减少系统调用,提高性能。 显著提升写入性能。 必须记得调用 Flush(),否则数据可能丢失。
    bufio.Scanner 需要方便地按行、按单词等“标记”来处理文本流。 API 简洁易用,代码可读性高,错误处理方便。 功能相对固定,不如 Reader 灵活。不适合读取二进制流。

    Go语言的输入与输出格式化功能非常强大,通过 fmt 包和 bufio 包,可以满足各种输入输出需求。掌握这些函数和格式化动词,有助于编写清晰、易读的 Go 程序。

    到此这篇关于Go基础:输入与输出格式化详解的文章就介绍到这了,更多相关Go输入与输出格式化内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

    您可能感兴趣的文章:

    • Go语言fmt库详解与应用实例(格式化输入输出功能)
    • golang 格式化输入输出操作
    • Go语言IO输入输出底层原理及文件操作API
    • Go语言标准输入输出库的基本使用教程
    • Go语言中的格式化占位符的实现示例
    • 在Go中格式化字符串的几种常用方法

    站内搜索