自定义分割函数是 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、请求更多数据,还是结束扫描。