在 Go 中逐行读取文件

Reading a file line by line in Go

提问人:g06lin 提问时间:1/6/2012 最后编辑:Amin Shojaeig06lin 更新时间:12/7/2022 访问量:559813

问:

我无法在 Go 中找到函数。file.ReadLine

如何逐行读取文件?

字符串 文件 解析 go

评论

9赞 Malcolm 10/17/2013
从 Go1.1 开始,bufio。扫描仪是执行此操作的最佳方法。

答:

192赞 user811773 1/6/2012 #1

注意:在早期版本的 Go 中,公认的答案是正确的。请参阅得票最高的答案包含实现此目的的最新惯用方法。

包中有函数 ReadLinebufio

请注意,如果该行不适合读取缓冲区,该函数将返回一个不完整的行。如果您希望始终通过对函数的单次调用来读取程序中的整行,则需要将该函数封装到您自己的函数中,该函数在 for 循环中调用。ReadLineReadLine

bufio.ReadString('\n')不完全等同于 因为无法处理文件最后一行不以换行符结尾的情况。ReadLineReadString

评论

48赞 Michael Whatcott 3/19/2014
来自文档:“ReadLine 是一个低级行读取原语。大多数调用方应改用 ReadBytes('\n') 或 ReadString('\n') 或使用 Scanner。
14赞 Charlie Parker 8/18/2014
@mdwhatcott为什么它是“低级读行原语”很重要呢?这如何得出“大多数调用方应改用 ReadBytes('\n') 或 ReadString('\n') 或使用 Scanner 的结论。
16赞 Michael Whatcott 8/19/2014
@CharlieParker - 不确定,只是引用文档来添加上下文。
13赞 eduncan911 10/19/2014
来自同一个文档..“如果 ReadString 在找到分隔符之前遇到错误,它会返回错误之前读取的数据和错误本身(通常是 io.EOF)。因此,您可以检查io。EOF错误,知道你已经完成了。
2赞 Davos 10/28/2019
(5年后澄清意见)当某件事说它是低级的,大多数调用者应该使用其他一些高级的东西时,这是因为高级的东西可能更容易使用,正确地实现了低级的东西,并提供了一个抽象层,以便对低级事物的重大更改的影响被包含在修复高级事物的用法上, 而不是直接调用低级事物的所有用户代码。
64赞 Malcolm 8/31/2012 #2

编辑:从 go1.1 开始,惯用的解决方案是使用 bufio。扫描器

我写了一种方法来轻松读取文件中的每一行。The Readln(*bufio.Reader) 函数从底层 bufio 返回一行 (sans \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 读取文件中的每一行。以下代码读取文件中的每一行,并将每一行输出到 stdout。

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)
}

干杯!

评论

23赞 Malcolm 7/9/2013
我在 Go 1.1 发布之前就写了这个答案。Go 1.1 在 stdlib 中有一个 Scanner 包。它提供了与我的答案相同的功能。我建议使用 Scanner 而不是我的答案,因为 Scanner 在 stdlib 中。祝您黑客愉快!:-)
6赞 kroisse 10/20/2012 #3

布菲奥。Reader.ReadLine() 运行良好。但是,如果要按字符串读取每一行,请尝试使用 ReadString('\n')。它不需要重新发明轮子。

13赞 lzap 12/3/2012 #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
    }
  }

评论

0赞 Thomas S. 1/15/2022
使用什么编码将字节转换为字符串?
4赞 cyber 5/10/2013 #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                                                   
}                                    
937赞 Stefan Arentz 5/18/2013 #6

在 Go 1.1 及更高版本中,最简单的方法是使用 bufio。扫描仪。下面是从文件中读取行的简单示例:

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 int = longLineLen  // your required line length
buf := make([]byte, maxCapacity)
scanner.Buffer(buf, maxCapacity)

for scanner.Scan() {
...

评论

47赞 Evan Plumlee 8/18/2013
由于 OP 要求扫描文件,因此先扫描文件句柄,然后再扫描文件句柄是微不足道的:file, _ := os.Open("/path/to/file.csv")scanner := bufio.NewScanner(file)
26赞 eduncan911 10/21/2014
问题是 Scanner.Scan() 被限制在每行 4096 []字节的缓冲区大小中。您将收到错误,即如果行太长。在这种情况下,您必须使用 bufio。ReaderLine() 或 ReadString()。bufio.ErrTooLongbufio.Scanner: token too long
12赞 sethvargo 11/24/2014
只是我的 0.02 美元 - 这是页面上最正确的答案:)
11赞 Kokizzu 1/21/2015
从源代码来看,它现在限制为 64 KB,而不是 4 KB,请参阅:golang.org/src/bufio/scan.go?#L71
11赞 Alex Robinson 3/24/2016
您可以使用其 Buffer() 方法将 Scanner 配置为处理更长的行: golang.org/pkg/bufio/#Scanner.Buffer
22赞 Kokizzu 1/21/2015 #7

这个要点的例子

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 的缓冲区时,这会产生错误。

发生这种情况时,我所做的就是使用 create 和连接我自己的缓冲区,或者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
}

评论

0赞 Kokizzu 2/25/2015
关心解释为什么?-1
0赞 Decebal 4/18/2016
我想是的;这个解决方案有点过于复杂了,不是吗?
2赞 zuzuleinen 11/23/2016 #8

在下面的代码中,我从 CLI 中读取兴趣,直到用户按回车键并且我正在使用 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)
1赞 0DAYanc 12/18/2017 #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)

var name string = ReadFromStdin()

println(name)
45赞 zouying 2/3/2018 #10

有两种常见的方法可以逐行读取文件。

  1. 使用 bufio。扫描器
  2. 使用 ReadString/ReadBytes/...在布菲奥。读者

在我的测试用例中,~250MB,~2,500,000 行,bufio。扫描仪(使用时间:0.395491384s)比 bufio 快。Reader.ReadString(time_used:0.446867622s)。

源代码: https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line

使用 bufio 读取文件。扫描器

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 读取文件。读者

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
    }
}

评论

2赞 konrad 11/26/2018
请注意,如果文件中的最后一行不以换行符结尾,则此示例将不会读取该文件。 将返回最后一行,在本例中为。bufio.ReaderReadStringio.EOF
0赞 Justin 7/8/2020
代码使用 bufio。读者将丢失文件的最后一行。如果 err== io。EOF 它不能直接中断,该时间线具有文件的最后一行。
8赞 pythoner 6/20/2020 #11

另一种方法是使用 和 库来读取整个文件的字节,将它们转换为字符串,并使用 “”(换行符)字符作为分隔符来拆分它们,例如:io/ioutilstrings\n

import (
    "io/ioutil"
    "strings"
)

func main() {
    bytesRead, _ := ioutil.ReadFile("something.txt")
    fileContent := string(bytesRead)
    lines := strings.Split(fileContent, "\n")
}

从技术上讲,您不是逐行读取文件,但是您可以使用此技术解析每一行。此方法适用于较小的文件。如果您尝试解析大型文件,请使用逐行读取的技术之一。

评论

8赞 donatJ 6/10/2021
像这样将整个文件读取到内存中,然后将其分解,对于大文件来说可能非常昂贵。
2赞 user2233706 2/10/2023
os.ReadFile()似乎也在做同样的事情。
-2赞 Arun 12/18/2020 #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/

评论

9赞 chahu418 4/21/2021
这个例子很好地演示了嵌入包,但我认为你的回答并没有解决问题的核心。OP 希望逐行读取文件。即便如此,你还是为他提供了一种非常好和惯用的方式,让他阅读整个文件。
0赞 PePa 12/10/2023
这是编译时嵌入!
0赞 Srinivas Kothuri 11/30/2022 #13

Scan* 功能在这里非常有用。这是 go-lang 文档中用于扫描文件中行的 word scanner 示例的略微修改版本。

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    // An artificial input source.
    const input = "Now is the winter of our discontent,\nMade glorious summer by this sun of York.\n"
    scanner := bufio.NewScanner(strings.NewReader(input))
    // Set the split function for the scanning operation.
    scanner.Split(bufio.ScanLines)
    // Count the lines.
    count := 0
    for scanner.Scan() {
        fmt.Println(scanner.Text())
        count++
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading input:", err)
    }
    fmt.Printf("%d\n", count)
}