在 Go 中逐行读取文件
Posted
技术标签:
【中文标题】在 Go 中逐行读取文件【英文标题】:Reading a file line by line in Go 【发布时间】:2012-02-04 03:44:52 【问题描述】:我在 Go 中找不到 file.ReadLine
函数。我可以弄清楚如何快速写一个,但我只是想知道我是否在这里忽略了一些东西。如何逐行读取文件?
【问题讨论】:
从 Go1.1 开始,bufio.Scanner 是执行此操作的最佳方式。 【参考方案1】:注意:接受的答案在 Go 的早期版本中是正确的。 See the highest voted answer 包含实现此目的的最新惯用方式。
包bufio
中有函数ReadLine。
请注意,如果该行不适合读取缓冲区,该函数将返回一个不完整的行。如果您想始终通过一次调用函数来读取程序中的整行,则需要将 ReadLine
函数封装到您自己的函数中,该函数在 for 循环中调用 ReadLine
。
bufio.ReadString('\n')
不完全等同于ReadLine
,因为ReadString
无法处理文件最后一行不以换行符结尾的情况。
【讨论】:
来自文档:“ReadLine 是一种低级的行读取原语。大多数调用者应该使用 ReadBytes('\n') 或 ReadString('\n') 或使用扫描仪。” @mdwhatcott 为什么它是一个“低级行阅读原语”很重要?这如何得出“大多数调用者应该使用 ReadBytes('\n') 或 ReadString('\n') 或使用 Scanner。”的结论? @CharlieParker - 不确定,只是引用文档来添加上下文。 来自同一个文档。“如果 ReadString 在找到分隔符之前遇到错误,它会返回错误之前读取的数据和错误本身(通常是 io.EOF)。”所以你只需检查 io.EOF 错误就知道你已经完成了。 请注意,由于系统调用中断,读取或写入可能会失败,这会导致读取或写入的字节数少于预期。【参考方案2】:编辑:从 go1.1 开始,惯用的解决方案是使用 bufio.Scanner
我编写了一种方法来轻松读取文件中的每一行。 Readln(*bufio.Reader) 函数从底层 bufio.Reader 结构返回一行(无 \n)。
// Readln returns a single line (without the ending \n)
// from the input buffered reader.
// An error is returned iff there is an error with the
// buffered reader.
func Readln(r *bufio.Reader) (string, error)
var (isPrefix bool = true
err error = nil
line, ln []byte
)
for isPrefix && err == nil
line, isPrefix, err = r.ReadLine()
ln = append(ln, line...)
return string(ln),err
您可以使用 Readln 从文件中读取每一行。以下代码读取文件中的每一行并将每一行输出到标准输出。
f, err := os.Open(fi)
if err != nil
fmt.Printf("error opening file: %v\n",err)
os.Exit(1)
r := bufio.NewReader(f)
s, e := Readln(r)
for e == nil
fmt.Println(s)
s,e = Readln(r)
干杯!
【讨论】:
我在 Go 1.1 出来之前写了这个答案。 Go 1.1 在 stdlib 中有一个 Scanner 包。提供与我的答案相同的功能。我建议使用 Scanner 而不是我的答案,因为 Scanner 在 stdlib 中。快乐黑客! :-)【参考方案3】:bufio.Reader.ReadLine() 运行良好。但如果你想按字符串读取每一行,请尝试使用ReadString('\n')。它不需要重新发明***。
【讨论】:
【参考方案4】:您也可以使用 ReadString 和 \n 作为分隔符:
f, err := os.Open(filename)
if err != nil
fmt.Println("error opening file ", err)
os.Exit(1)
defer f.Close()
r := bufio.NewReader(f)
for
path, err := r.ReadString(10) // 0x0A separator = newline
if err == io.EOF
// do something here
break
else if err != nil
return err // if you return error
【讨论】:
【参考方案5】:// strip '\n' or read until EOF, return error if read error
func readline(reader io.Reader) (line []byte, err error)
line = make([]byte, 0, 100)
for
b := make([]byte, 1)
n, er := reader.Read(b)
if n > 0
c := b[0]
if c == '\n' // end of line
break
line = append(line, c)
if er != nil
err = er
return
return
【讨论】:
【参考方案6】: 推荐的答案 Go Language在 Go 1.1 和更新版本中,最简单的方法是使用 bufio.Scanner
。这是一个从文件中读取行的简单示例:
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main()
file, err := os.Open("/path/to/file.txt")
if err != nil
log.Fatal(err)
defer file.Close()
scanner := bufio.NewScanner(file)
// optionally, resize scanner's capacity for lines over 64K, see next example
for scanner.Scan()
fmt.Println(scanner.Text())
if err := scanner.Err(); err != nil
log.Fatal(err)
这是逐行读取Reader
的最简洁方式。
有一个警告:扫描仪会在行长超过 65536 个字符时出错。如果您知道您的行长大于 64K,请使用Buffer()
方法来增加扫描仪的容量:
...
scanner := bufio.NewScanner(file)
const maxCapacity = longLineLen // your required line length
buf := make([]byte, maxCapacity)
scanner.Buffer(buf, maxCapacity)
for scanner.Scan()
...
【讨论】:
并且由于 OP 要求扫描文件,因此首先file, _ := os.Open("/path/to/file.csv")
然后扫描文件句柄将是微不足道的:scanner := bufio.NewScanner(file)
问题是 Scanner.Scan() 限制在每行 4096 [] 字节的缓冲区大小。你会得到bufio.ErrTooLong
错误,如果行太长,则为bufio.Scanner: token too long
。在这种情况下,您必须使用 bufio.ReaderLine() 或 ReadString()。
只是我的 0.02 美元 - 这是页面上最正确的答案 :)
从源代码现在限制为 64 KB 而不是 4 KB,请参阅:golang.org/src/bufio/scan.go?#L71
您可以配置 Scanner 以使用其 Buffer() 方法处理更长的行:golang.org/pkg/bufio/#Scanner.Buffer【参考方案7】:
来自gist的示例
func readLine(path string)
inFile, err := os.Open(path)
if err != nil
fmt.Println(err.Error() + `: ` + path)
return
defer inFile.Close()
scanner := bufio.NewScanner(inFile)
for scanner.Scan()
fmt.Println(scanner.Text()) // the line
但是当有一行大于 Scanner 的缓冲区时会出错。
当这种情况发生时,我所做的是使用 reader := bufio.NewReader(inFile)
创建并连接我自己的缓冲区,或者使用 ch, err := reader.ReadByte()
或 len, err := reader.Read(myBuffer)
我使用的另一种方式(将 os.Stdin 替换为上面的文件),当行很长时(isPrefix)连接并忽略空行:
func readLines() []string
r := bufio.NewReader(os.Stdin)
bytes := []byte
lines := []string
for
line, isPrefix, err := r.ReadLine()
if err != nil
break
bytes = append(bytes, line...)
if !isPrefix
str := strings.TrimSpace(string(bytes))
if len(str) > 0
lines = append(lines, str)
bytes = []byte
if len(bytes) > 0
lines = append(lines, string(bytes))
return lines
【讨论】:
想解释一下为什么-1
?
我认为这个解决方案有点过于复杂,不是吗?【参考方案8】:
在下面的代码中,我从 CLI 读取兴趣,直到用户按 Enter 并且我正在使用 Readline:
interests := make([]string, 1)
r := bufio.NewReader(os.Stdin)
for true
fmt.Print("Give me an interest:")
t, _, _ := r.ReadLine()
interests = append(interests, string(t))
if len(t) == 0
break;
fmt.Println(interests)
【讨论】:
【参考方案9】:import (
"bufio"
"os"
)
var (
reader = bufio.NewReader(os.Stdin)
)
func ReadFromStdin() string
result, _ := reader.ReadString('\n')
witl := result[:len(result)-1]
return witl
这是一个带有函数 ReadFromStdin()
的示例,它类似于 fmt.Scan(&name)
,但它接受所有带有空格的字符串,例如:“Hello My Name Is ...”
var name string = ReadFromStdin()
println(name)
【讨论】:
【参考方案10】:有两种常见的逐行读取文件的方法。
-
使用 bufio.Scanner
在 bufio.Reader 中使用 ReadString/ReadBytes/...
在我的测试用例中,~250MB,~2,500,000 行,bufio.Scanner(time used: 0.395491384s) 比 bufio.Reader.ReadString(time_used: 0.446867622s) 快。
源码:https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line
使用 bufio.Scanner 读取文件,
func scanFile()
f, err := os.OpenFile(logfile, os.O_RDONLY, os.ModePerm)
if err != nil
log.Fatalf("open file error: %v", err)
return
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan()
_ = sc.Text() // GET the line string
if err := sc.Err(); err != nil
log.Fatalf("scan file error: %v", err)
return
使用bufio.Reader读取文件,
func readFileLines()
f, err := os.OpenFile(logfile, os.O_RDONLY, os.ModePerm)
if err != nil
log.Fatalf("open file error: %v", err)
return
defer f.Close()
rd := bufio.NewReader(f)
for
line, err := rd.ReadString('\n')
if err != nil
if err == io.EOF
break
log.Fatalf("read file line error: %v", err)
return
_ = line // GET the line string
【讨论】:
请注意,这个bufio.Reader
示例不会读取文件中的最后一行,如果它不以换行符结尾。在这种情况下,ReadString
将返回最后一行和 io.EOF
。
使用bufio.Reader的代码会丢失文件的最后一行。如果 err== io.EOF 它不能直接中断,则该时间行包含文件的最后一行。【参考方案11】:
另一种方法是使用io/ioutil
和strings
库读取整个文件的字节,将它们转换为字符串并使用“\n
”(换行符)字符作为分隔符将它们拆分,例如:
import (
"io/ioutil"
"strings"
)
func main()
bytesRead, _ := ioutil.ReadFile("something.txt")
file_content := string(bytesRead)
lines := strings.Split(file_content, "\n")
从技术上讲,您不是逐行读取文件,但是您可以使用此技术解析每一行。此方法适用于较小的文件。如果您尝试解析大型文件,请使用一种逐行读取的技术。
【讨论】:
像这样将整个文件读入内存然后分解它对于大文件来说可能会非常昂贵。【参考方案12】:在新版本的 Go 1.16 中,我们可以使用 package embed 来读取文件内容,如下所示。
package main
import _"embed"
func main()
//go:embed "hello.txt"
var s string
print(s)
//go:embed "hello.txt"
var b []byte
print(string(b))
//go:embed hello.txt
var f embed.FS
data, _ := f.ReadFile("hello.txt")
print(string(data))
更多详情请通过https://tip.golang.org/pkg/embed/ 和 https://golangtutorial.dev/tips/embed-files-in-go/
【讨论】:
这个例子很好地展示了embed
包,但我认为你的回答没有解决问题的核心。 OP 想要逐行读取文件。即便如此,您还是为他提供了一种非常棒且惯用的方式让他阅读整个文件。以上是关于在 Go 中逐行读取文件的主要内容,如果未能解决你的问题,请参考以下文章