在 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/ioutilstrings 库读取整个文件的字节,将它们转换为字符串并使用“\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 中逐行读取文件的主要内容,如果未能解决你的问题,请参考以下文章

在 Swift 中逐行读取文本文件?

在 C# 中逐行读取文件

如何在 Julia 中逐行读取文件?

在 Swift 中逐行读取文件/URL

在 VBA 中逐行读取/解析文本文件

golang 在#golang中逐行读取文件