bufio.NewScanner 详解

bufio.NewScanner 是 Go 语言中用于逐行读取数据的强大工具,它提供了高效、便捷的文本扫描功能。

基本用法

package main

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

func main() {
    // 创建输入源
    input := strings.NewReader("Hello\nWorld\nGo\n")
    
    // 创建 Scanner
    scanner := bufio.NewScanner(input)
    
    // 逐行扫描
    for scanner.Scan() {
        fmt.Println(scanner.Text()) // 获取当前行的文本
    }
    
    // 检查错误
    if err := scanner.Err(); err != nil {
        fmt.Println("Error:", err)
    }
}

核心方法

1. Scan() 方法

func (s *Scanner) Scan() bool
  • 推进扫描器到下一个 token
  • 返回 true 如果找到 token,false 如果到达输入末尾或发生错误
  • 自动处理不同平台的换行符(\n, \r\n

2. Text() 方法

func (s *Scanner) Text() string
  • 返回最近一次 Scan() 调用产生的 token
  • token 是去除行分隔符后的字符串

3. Bytes() 方法

func (s *Scanner) Bytes() []byte
  • 返回最近一次 Scan() 调用产生的 token 的字节切片
  • 注意:返回的字节切片在下一次 Scan() 调用后可能失效

4. Err() 方法

func (s *Scanner) Err() error
  • 返回扫描过程中遇到的第一个非 EOF 错误

高级配置

自定义分割函数

package main

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

// 按单词分割
func main() {
    input := strings.NewReader("Hello World Go Programming")
    scanner := bufio.NewScanner(input)
    
    // 设置自定义分割函数(按空格分割)
    scanner.Split(bufio.ScanWords)
    
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

内置分割函数

// 按行分割(默认)
scanner.Split(bufio.ScanLines)

// 按单词分割
scanner.Split(bufio.ScanWords)

// 按字符分割
scanner.Split(bufio.ScanRunes)

// 按字节分割
scanner.Split(bufio.ScanBytes)

自定义分割函数示例

package main

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

// 自定义分割函数:按逗号分割
func commaSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
    if atEOF && len(data) == 0 {
        return 0, nil, nil
    }
    
    if i := bytes.IndexByte(data, ','); i >= 0 {
        // 找到逗号,返回逗号前的部分
        return i + 1, data[0:i], nil
    }
    
    // 如果到达文件末尾,返回剩余数据
    if atEOF {
        return len(data), data, nil
    }
    
    // 请求更多数据
    return 0, nil, nil
}

func main() {
    input := strings.NewReader("apple,banana,cherry,date")
    scanner := bufio.NewScanner(input)
    scanner.Split(commaSplit)
    
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

缓冲区大小配置

package main

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

func main() {
    input := strings.NewReader("Hello\nWorld\n")
    scanner := bufio.NewScanner(input)
    
    // 设置初始缓冲区大小(可选)
    buffer := make([]byte, 0, 64*1024) // 64KB
    scanner.Buffer(buffer, 1024*1024)  // 最大1MB
    
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

实际应用示例

1. 读取文件

package main

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

func main() {
    file, err := os.Open("data.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    
    scanner := bufio.NewScanner(file)
    lineNumber := 1
    
    for scanner.Scan() {
        fmt.Printf("%d: %s\n", lineNumber, scanner.Text())
        lineNumber++
    }
    
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading file:", err)
    }
}

2. 处理标准输入

package main

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

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    fmt.Println("Enter text (Ctrl+D to end):")
    
    for scanner.Scan() {
        text := scanner.Text()
        if text == "exit" {
            break
        }
        fmt.Println("You entered:", text)
    }
    
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading standard input:", err)
    }
}

3. 统计文件信息

package main

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

func main() {
    file, err := os.Open("document.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    
    scanner := bufio.NewScanner(file)
    
    var lines, words, chars int
    
    for scanner.Scan() {
        line := scanner.Text()
        lines++
        chars += len(line)
        
        // 简单统计单词数(按空格分割)
        wordScanner := bufio.NewScanner(strings.NewReader(line))
        wordScanner.Split(bufio.ScanWords)
        for wordScanner.Scan() {
            words++
        }
    }
    
    fmt.Printf("Lines: %d, Words: %d, Characters: %d\n", lines, words, chars)
}

注意事项

  1. 性能考虑:对于大文件,Scanner 比 ioutil.ReadFile 更节省内存
  2. 错误处理:总是检查 scanner.Err()
  3. 缓冲区重用Text()Bytes() 返回的数据可能被后续扫描重用
  4. 长行处理:默认缓冲区大小为 64KB,对于超长行需要调整缓冲区大小

常见问题解决

处理超长行

scanner := bufio.NewScanner(file)
maxCapacity := 1024 * 1024 // 1MB
buf := make([]byte, maxCapacity)
scanner.Buffer(buf, maxCapacity)

性能优化

对于性能敏感的场景,可以考虑使用 bufio.ReaderReadLine() 方法,但 Scanner 在大多数情况下已经足够高效且更易用。

bufio.NewScanner 是 Go 中处理文本输入的推荐方式,它提供了良好的性能、灵活的分割选项和简洁的 API。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容