Go/按行读取文件
最近在处理一些日志分析的任务, 需要按行读取文件. 虽然平时遇到这种需求我都习惯性地上 python, 但既然用了 go, 就顺便整理一下 go 里面几种读取文件的方式. 其实选择还挺多的, 根据实际情况选就好.
使用 bufio.Scanner
大部分情况下我都用这个. bufio.Scanner 的 api 很简洁, 会自动帮你去掉换行符, 而且内存效率也不错.
package main
import (
"bufio"
"log"
"os"
)
func main() {
f, err := os.Open("main.go")
if err != nil {
log.Fatalln(err)
}
defer f.Close()
// scanner, 启动!
scanner := bufio.NewScanner(f)
for scanner.Scan() {
// 这里注意, scanner.Text() 不会返回换行符
log.Println(scanner.Text())
}
if scanner.Err() != nil {
log.Fatalln(scanner.Err())
}
}
这里要注意一点, scanner.Text() 返回的字符串是不带换行符的, 这在大多数情况下很方便, 不用再去手动 trim.
不过 scanner 有个限制, 默认单行最大只能处理 64kb. 如果你的文件某一行特别长(比如处理某些日志或者 json), 可以通过 scanner.Buffer() 来调整缓冲区大小:
scanner := bufio.NewScanner(f)
// 设置初始缓冲区为 1mb,最大 10mb
buf := make([]byte, 1024*1024)
max := 10 * 1024 * 1024
scanner.Buffer(buf, max)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
使用 bufio.Reader.ReadString
有时候你可能需要保留换行符, 或者想要更细粒度的控制, 这时候 bufio.Reader.ReadString 就派上用场了.
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
)
func main() {
f, err := os.Open("main.go")
if err != nil {
log.Fatalln(err)
}
defer f.Close()
reader := bufio.NewReader(f)
for {
// ReadString 读取直到遇到分隔符('\n'). 返回的字符串包含换行符.
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
// 处理最后一行(可能没有换行符)
if len(line) > 0 {
fmt.Printf("%s", line)
}
break
}
log.Fatalln(err)
}
fmt.Printf("%s", line)
}
}
和 scanner 不同, ReadString 返回的字符串是包含换行符的, 记得处理一下. 另外你可以传入任意分隔符, 不一定要用 \n, 这个比较灵活. 这种方式对超长行没有限制, 不过需要手动处理 eof, 写起来相对繁琐一些.
使用 bufio.Reader.ReadLine
如果你需要更精确的控制, 比如要处理特别长的行, 可以用 ReadLine. 它返回的是 []byte 而不是字符串, 性能会好一些.
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
)
func main() {
f, err := os.Open("main.go")
if err != nil {
log.Fatalln(err)
}
defer f.Close()
reader := bufio.NewReader(f)
for {
// ReadLine 返回字节切片,不包含换行符. Prefix 表示是否读取了完整的行. 如果为 true,说明行太长,需要多次读取.
line, _, err := reader.ReadLine()
if err != nil {
if err == io.EOF {
break
}
log.Fatalln(err)
}
fmt.Printf("%s\n", string(line))
}
}