bufio.Scanner 自定义分割函数详解

自定义分割函数是 bufio.Scanner 最强大的功能之一,它允许你完全控制如何将输入数据分割成 token。

分割函数签名

type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)

参数说明:

  • data []byte:当前可用的数据片段
  • atEOF bool:是否已到达输入流的末尾
  • 返回值:
    • advance int:从 data 开头前进多少字节
    • token []byte:提取到的 token(可为 nil)
    • err error:错误信息

分割函数的工作流程

package main

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

// 可视化分割过程
func debugSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
    fmt.Printf("DEBUG: data=%q, atEOF=%v\n", string(data), atEOF)
    
    if atEOF && len(data) == 0 {
        fmt.Println("DEBUG: End of input")
        return 0, nil, nil
    }
    
    // 简单的换行分割
    if i := bytes.IndexByte(data, '\n'); i >= 0 {
        fmt.Printf("DEBUG: Found newline at position %d\n", i)
        return i + 1, data[0:i], nil
    }
    
    if atEOF {
        fmt.Println("DEBUG: Returning remaining data at EOF")
        return len(data), data, nil
    }
    
    fmt.Println("DEBUG: Need more data")
    return 0, nil, nil
}

func main() {
    input := strings.NewReader("line1\nline2\nline3")
    scanner := bufio.NewScanner(input)
    scanner.Split(debugSplit)
    
    for scanner.Scan() {
        fmt.Printf("RESULT: %q\n\n", scanner.Text())
    }
}

完整的分割函数模板

func customSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
    // 1. 检查是否到达文件末尾且没有数据
    if atEOF && len(data) == 0 {
        return 0, nil, nil
    }
    
    // 2. 查找分割符位置
    if i := bytes.IndexAny(data, "你的分割符"); i >= 0 {
        // 找到分割符,返回分割符前的内容
        return i + 1, data[0:i], nil
    }
    
    // 3. 如果到达文件末尾,返回剩余的所有数据
    if atEOF {
        return len(data), data, nil
    }
    
    // 4. 请求更多数据
    return 0, nil, nil
}

实际应用示例

1. 逗号分割(CSV 风格)

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 len(data) > 0 && data[0] == '"' {
        // 查找结束引号
        if i := bytes.IndexByte(data[1:], '"'); i >= 0 {
            endQuote := i + 1
            // 检查后面是否有逗号
            if endQuote+1 < len(data) && data[endQuote+1] == ',' {
                return endQuote + 2, data[0 : endQuote+1], nil
            }
        }
    }
    
    if atEOF {
        return len(data), data, nil
    }
    
    return 0, nil, nil
}

func main() {
    input := `name,age,city
"John, Doe",25,"New York, NY"
Jane,30,Chicago`
    
    scanner := bufio.NewScanner(strings.NewReader(input))
    scanner.Split(commaSplit)
    
    for scanner.Scan() {
        fmt.Printf("Field: %q\n", scanner.Text())
    }
}

2. 固定长度分割

func fixedLengthSplit(length int) bufio.SplitFunc {
    return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        if atEOF && len(data) == 0 {
            return 0, nil, nil
        }
        
        // 如果数据足够长,返回指定长度的 token
        if len(data) >= length {
            return length, data[0:length], nil
        }
        
        // 如果到达文件末尾,返回剩余数据(即使不足长度)
        if atEOF {
            return len(data), data, nil
        }
        
        // 等待更多数据
        return 0, nil, nil
    }
}

func main() {
    input := "1234567890abcdefghij"
    scanner := bufio.NewScanner(strings.NewReader(input))
    scanner.Split(fixedLengthSplit(5))
    
    for scanner.Scan() {
        fmt.Printf("Chunk: %q\n", scanner.Text())
    }
}
// 输出:
// Chunk: "12345"
// Chunk: "67890"
// Chunk: "abcde"
// Chunk: "fghij"

3. 正则表达式分割

import "regexp"

func regexSplit(pattern string) bufio.SplitFunc {
    re := regexp.MustCompile(pattern)
    
    return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        if atEOF && len(data) == 0 {
            return 0, nil, nil
        }
        
        // 查找匹配位置
        if loc := re.FindIndex(data); loc != nil {
            advance = loc[1]
            token = data[loc[0]:loc[1]]
            return advance, token, nil
        }
        
        if atEOF {
            return len(data), data, nil
        }
        
        return 0, nil, nil
    }
}

func main() {
    input := "abc123def456ghi789"
    scanner := bufio.NewScanner(strings.NewReader(input))
    scanner.Split(regexSplit(`[a-z]+`)) // 匹配字母序列
    
    for scanner.Scan() {
        fmt.Printf("Match: %q\n", scanner.Text())
    }
}
// 输出:
// Match: "abc"
// Match: "def"
// Match: "ghi"

4. 多字符分隔符分割

func multiByteDelimiterSplit(delimiter []byte) bufio.SplitFunc {
    return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        if atEOF && len(data) == 0 {
            return 0, nil, nil
        }
        
        // 查找多字节分隔符
        if i := bytes.Index(data, delimiter); i >= 0 {
            // 返回分隔符前的内容,跳过整个分隔符
            return i + len(delimiter), data[0:i], nil
        }
        
        if atEOF {
            return len(data), data, nil
        }
        
        return 0, nil, nil
    }
}

func main() {
    input := "apple||banana||cherry||date"
    scanner := bufio.NewScanner(strings.NewReader(input))
    scanner.Split(multiByteDelimiterSplit([]byte("||")))
    
    for scanner.Scan() {
        fmt.Printf("Item: %q\n", scanner.Text())
    }
}
// 输出:
// Item: "apple"
// Item: "banana"
// Item: "cherry"
// Item: "date"

5. 智能空白分割(处理多个连续空白)

func smartWhitespaceSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
    // 跳过前导空白
    start := 0
    for start < len(data) && (data[start] == ' ' || data[start] == '\t') {
        start++
    }
    
    if start >= len(data) {
        if atEOF {
            return len(data), nil, nil
        }
        return 0, nil, nil
    }
    
    // 查找下一个空白
    for i := start; i < len(data); i++ {
        if data[i] == ' ' || data[i] == '\t' {
            // 找到空白,返回非空白部分
            return i + 1, data[start:i], nil
        }
    }
    
    // 到达末尾,返回剩余的非空白数据
    if atEOF {
        return len(data), data[start:], nil
    }
    
    // 请求更多数据
    return 0, nil, nil
}

func main() {
    input := "   hello    world   go   programming   "
    scanner := bufio.NewScanner(strings.NewReader(input))
    scanner.Split(smartWhitespaceSplit)
    
    for scanner.Scan() {
        fmt.Printf("Word: %q\n", scanner.Text())
    }
}
// 输出:
// Word: "hello"
// Word: "world"
// Word: "go"
// Word: "programming"

错误处理示例

func validatedSplit(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 {
        token := data[0:i]
        
        // 验证 token 长度
        if len(token) > 100 {
            return 0, nil, fmt.Errorf("token too long: %d bytes", len(token))
        }
        
        // 验证 token 内容
        if bytes.Contains(token, []byte("\x00")) {
            return 0, nil, fmt.Errorf("invalid character in token")
        }
        
        return i + 1, token, nil
    }
    
    if atEOF {
        token := data
        if len(token) > 100 {
            return 0, nil, fmt.Errorf("token too long: %d bytes", len(token))
        }
        return len(data), token, nil
    }
    
    return 0, nil, nil
}

func main() {
    input := "normal;too_long_" + strings.Repeat("x", 100) + ";valid"
    scanner := bufio.NewScanner(strings.NewReader(input))
    scanner.Split(validatedSplit)
    
    for scanner.Scan() {
        fmt.Printf("Token: %q\n", scanner.Text())
    }
    
    if err := scanner.Err(); err != nil {
        fmt.Printf("Error: %v\n", err)
    }
}

性能优化技巧

1. 避免不必要的内存分配

// 不好的实现:每次创建新切片
func inefficientSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
    if i := bytes.IndexByte(data, '|'); i >= 0 {
        // 这会创建新的底层数组
        token := make([]byte, i)
        copy(token, data[0:i])
        return i + 1, token, nil
    }
    // ...
}

// 好的实现:直接返回原数据的切片
func efficientSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
    if i := bytes.IndexByte(data, '|'); i >= 0 {
        // 直接返回原数据的视图
        return i + 1, data[0:i], nil
    }
    // ...
}

2. 使用查找表加速

func fastDelimiterSplit(delimiters string) bufio.SplitFunc {
    // 创建查找表
    delimiterSet := make(map[byte]bool)
    for i := 0; i < len(delimiters); i++ {
        delimiterSet[delimiters[i]] = true
    }
    
    return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        if atEOF && len(data) == 0 {
            return 0, nil, nil
        }
        
        // 使用查找表快速检查
        for i, b := range data {
            if delimiterSet[b] {
                return i + 1, data[0:i], nil
            }
        }
        
        if atEOF {
            return len(data), data, nil
        }
        
        return 0, nil, nil
    }
}

调试技巧

func createDebugSplit(originalSplit bufio.SplitFunc) bufio.SplitFunc {
    return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        advance, token, err = originalSplit(data, atEOF)
        
        fmt.Printf("Split: data=%q, atEOF=%v -> advance=%d, token=%q, err=%v\n",
            string(data), atEOF, advance, string(token), err)
        
        return advance, token, err
    }
}

// 使用方式
scanner.Split(createDebugSplit(bufio.ScanLines))

自定义分割函数的关键在于理解 Scanner 的工作机制:它会在需要时多次调用分割函数,每次提供当前可用的数据片段。分割函数需要决定是返回一个 token、请求更多数据,还是结束扫描。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容