← io 包 | embed 包 →

bufio 包 - Go 缓冲 I/O 详解

bufio 包提供了带缓冲的 I/O 操作,通过减少系统调用次数来显著提升 I/O 性能。理解 bufio 是高效处理文件、网络等 I/O 操作的关键。

📌 核心概念

📖

Reader

缓冲读取

ReadString
✍️

Writer

缓冲写入

WriteString
🔍

Scanner

文本扫描

Scan()

性能提升

减少系统调用

10x 性能

bufio.Reader 缓冲读取

📝 Reader 基础使用

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    // 从字符串读取
    reader := bufio.NewReader(strings.NewReader("Hello, World!\n"))
    
    // 读取到分隔符
    line, _ := reader.ReadString('\n')
    fmt.Println(line)
    
    // 读取单个字节
    b, _ := reader.ReadByte()
    fmt.Printf("Byte: %c\n", b)
    
    // 读取到指定分隔符(丢弃)
    reader.ReadBytes('\n')
    
    // 读取一行(不包括\n)
    reader2 := bufio.NewReader(strings.NewReader("Line 1\nLine 2\n"))
    line2, _ := reader2.ReadLine()
    fmt.Printf("Line: %s\n", line2)
    
    // 从文件读取
    file, _ := os.Open("test.txt")
    defer file.Close()
    
    bufReader := bufio.NewReader(file)
    content, _ := bufReader.ReadString('\n')
    fmt.Println(content)
}

💡 Reader 要点

  • 默认缓冲: 默认缓冲区大小为 4KB
  • 自定义大小: NewReaderSize 可指定大小
  • Peek: 预览数据而不消耗
  • Discard: 跳过指定字节数

bufio.Writer 缓冲写入

📝 Writer 基础使用

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "os"
)

func main() {
    // 写入 bytes.Buffer
    var buf bytes.Buffer
    writer := bufio.NewWriter(&buf)
    
    // 缓冲写入
    writer.WriteString("Hello, ")
    writer.WriteString("World!\n")
    
    // 必须刷新才能看到数据
    writer.Flush()
    fmt.Println(buf.String())
    
    // 写入文件
    file, _ := os.Create("output.txt")
    defer file.Close()
    
    bufWriter := bufio.NewWriter(file)
    bufWriter.WriteString("File content\n")
    bufWriter.Flush() // 必须刷新
    
    // 可用字节数
    fmt.Printf("Available: %d\n", bufWriter.Available())
    
    // 缓冲大小
    fmt.Printf("Size: %d\n", bufWriter.BufferSize())
}

⚠️ Writer 注意事项

  • 必须 Flush: 数据在缓冲区,必须调用 Flush
  • 错误处理: Flush 可能返回错误
  • 资源清理: 使用 defer 确保 Flush 被调用

bufio.Scanner 文本扫描

📝 Scanner 逐行读取

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    // 逐行读取字符串
    text := "Line 1\nLine 2\nLine 3\n"
    scanner := bufio.NewScanner(strings.NewReader(text))
    
    for scanner.Scan() {
        line := scanner.Text()
        fmt.Println(line)
    }
    
    // 检查错误
    if err := scanner.Err(); err != nil {
        fmt.Printf("Scan error: %v\n", err)
    }
    
    // 从文件逐行读取
    file, _ := os.Open("test.txt")
    defer file.Close()
    
    scanner2 := bufio.NewScanner(file)
    lineNum := 0
    for scanner2.Scan() {
        lineNum++
        fmt.Printf("Line %d: %s\n", lineNum, scanner2.Text())
    }
}

📝 自定义分割函数

package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    // 按单词扫描
    text := "Hello world from Go"
    scanner := bufio.NewScanner(strings.NewReader(text))
    scanner.Split(bufio.ScanWords)
    
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    
    // 按 rune 扫描
    scanner2 := bufio.NewScanner(strings.NewReader("ABC"))
    scanner2.Split(bufio.ScanRunes)
    
    for scanner2.Scan() {
        fmt.Printf("Rune: %s\n", scanner2.Text())
    }
    
    // 自定义分割函数:按逗号分割
    commaSplit := func(data []byte, atEOF bool) (int, []byte, error) {
        for i := 0; i < len(data); i++ {
            if data[i] == ',' {
                return i + 1, data[:i], nil
            }
        }
        return 0, nil, nil
    }
    
    text2 := "apple,banana,orange,grape"
    scanner3 := bufio.NewScanner(strings.NewReader(text2))
    scanner3.Split(commaSplit)
    
    for scanner3.Scan() {
        fmt.Println(scanner3.Text())
    }
}

性能对比

📊 bufio vs 直接 I/O

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "time"
)

// 直接读取(无缓冲)
func readWithoutBuffer(filename string) error {
    file, _ := os.Open(filename)
    defer file.Close()
    
    buf := make([]byte, 1) // 每次 1 字节
    for {
        _, err := file.Read(buf)
        if err == io.EOF {
            break
        }
    }
    return nil
}

// 缓冲读取
func readWithBuffer(filename string) error {
    file, _ := os.Open(filename)
    defer file.Close()
    
    reader := bufio.NewReader(file)
    buf := make([]byte, 1)
    for {
        _, err := reader.Read(buf)
        if err == io.EOF {
            break
        }
    }
    return nil
}

func main() {
    // 创建测试文件
    file, _ := os.Create("test.dat")
    for i := 0; i < 100000; i++ {
        file.Write([]byte("0123456789\n"))
    }
    file.Close()
    
    // 测试无缓冲
    start := time.Now()
    readWithoutBuffer("test.dat")
    fmt.Printf("Without buffer: %v\n", time.Since(start))
    
    // 测试有缓冲
    start = time.Now()
    readWithBuffer("test.dat")
    fmt.Printf("With buffer: %v\n", time.Since(start))
    
    // 结果:缓冲读取快 100-1000 倍
}

实用模式

📝 文件处理模式

package main

import (
    "bufio"
    "fmt"
    "os"
)

// 模式 1: 大文件逐行处理
func processLargeFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        // 处理每一行...
        fmt.Println(line)
    }
    
    return scanner.Err()
}

// 模式 2: 高效写入文件
func writeLargeFile(filename string, lines []string) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    writer := bufio.NewWriter(file)
    for _, line := range lines {
        writer.WriteString(line + "\n")
    }
    return writer.Flush()
}

// 模式 3: 交互式输入
func readUserInput() string {
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("Enter something: ")
    input, _ := reader.ReadString('\n')
    return input[:len(input)-1] // 去掉换行符
}

func main() {
    processLargeFile("large.txt")
    writeLargeFile("output.txt", []string{"Line 1", "Line 2"})
    input := readUserInput()
    fmt.Printf("You entered: %s\n", input)
}

最佳实践

✅ bufio 使用建议

  • 大文件处理: 使用 Scanner 逐行读取
  • 高效写入: 使用 Writer 缓冲写入
  • 必须 Flush: Writer 用后必须调用 Flush
  • 错误检查: Scanner 用后检查 Err()
  • 自定义缓冲: 大记录使用 NewReaderSize

🚨 常见陷阱

  • 忘记 Flush: Writer 数据丢失
  • Scanner 限制: 默认最大行 64KB
  • 并发使用: Reader/Writer 不是并发安全的
  • 资源泄漏: 文件用后必须 Close

📖 延伸阅读