go中逐行读取文件

g06*_*lin 286 string parsing file line go

我无法file.ReadLine在Go中找到功能.我可以弄清楚如何快速写一个,但只是想知道我是否在这里忽略了一些东西.如何逐行读取文件?

Ste*_*ntz 525

在Go 1.1和更新版本中,最简单的方法是使用a 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)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}
Run Code Online (Sandbox Code Playgroud)

这是Reader逐行读取的最简洁方法.

有一点需要注意:扫描仪不能很好地处理超过65536个字符的行.如果这对你来说是一个问题,那么你应该自己动手Reader.Read().

  • 并且由于OP要求扫描一个文件,首先`file,_:= os.Open("/ path/to/file.csv")`然后扫描文件句柄是很简单的:`scanner: = bufio.NewScanner(file)` (38认同)
  • 不要忘记`defer file.Close()`. (13认同)
  • 问题是Scanner.Scan()受限于每行4096 []字节的缓冲区大小.你会得到`bufio.ErrTooLong`错误,如果行太长,那就是'bufio.Scanner:令牌太长了'.在这种情况下,您将必须使用bufio.ReaderLine()或ReadString(). (10认同)
  • 只需我0.02美元 - 这是页面上最正确的答案:) (4认同)
  • 这样做的问题是CSV文件的列中可能有换行符,但不表示行的结尾.如果您正在阅读CSV文件,则应使用[go go CSV package](https://golang.org/pkg/encoding/csv/) (4认同)
  • 您可以使用Buffer()方法将Scanner配置为处理更长的行:https://golang.org/pkg/bufio/#Scanner.Buffer (4认同)
  • 从源头来看,它现在限制为64 KB而不是4 KB,请参阅:http://golang.org/src/bufio/scan.go?#L71 (2认同)

小智 119

包中有ReadLine功能bufio.

请注意,如果该行不适合读取缓冲区,则该函数将返回不完整的行.如果你想通过一次调用函数来总是在程序中读取整行,你需要将ReadLine函数封装到你自己的函数中,该函数调用ReadLinefor循环.

bufio.ReadString('\n')并不完全等同于ReadLine因为ReadString当文件的最后一行没有以换行符结尾时无法处理这种情况.

  • 从文档:"ReadLine是一个低级行读取原语.大多数调用者应该使用ReadBytes('\n')或ReadString('\n')代替或使用扫描器." (33认同)
  • @mdwhatcott为什么它的"低级线读原语"很重要?如何得出结论:"大多数呼叫者应该使用ReadBytes('\n')或ReadString('\n')代替或使用扫描器."? (12认同)
  • @CharlieParker - 不确定,只是引用文档来添加上下文. (12认同)
  • 来自相同的文档.."如果ReadString在找到分隔符之前遇到错误,它将返回错误之前读取的数据和错误本身(通常是io.EOF)." 所以你可以检查io.EOF错误并知道你已经完成了. (10认同)
  • (5年后澄清评论)当某些东西说它是低级的并且大多数调用者应该使用其他一些高级的东西时,这是因为高级的东西可能更容易使用,正确地实现低级的东西,并提供抽象层,以便对低级事物的重大更改的影响包含在修复高级事物的使用中,而不是直接调用低级事物的所有用户代码。 (2认同)

a-h*_*a-h 73

使用:

  • reader.ReadString('\n')
    • 如果你不介意线路可能很长(即使用大量的RAM).它保持\n在返回的字符串的末尾.
  • reader.ReadLine()
    • 如果您关心限制RAM消耗,并且不介意处理线路大于读取器缓冲区大小的情况的额外工作.

我通过编写程序来测试建议的各种解决方案,以测试在其他答案中被识别为问题的场景:

  • 一个4MB行的文件.
  • 不以换行符结尾的文件.

我找到:

  • Scanner解决方案无法处理长线.
  • ReadLine解决方案实施起来很复杂.
  • ReadString解决方案是最简单,适用于大排长龙.

这是演示每个解决方案的代码,它可以通过go run main.go以下方式运行:

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "os"
)

func readFileWithReadString(fn string) (err error) {
    fmt.Println("readFileWithReadString")

    file, err := os.Open(fn)
    defer file.Close()

    if err != nil {
        return err
    }

    // Start reading from the file with a reader.
    reader := bufio.NewReader(file)

    var line string
    for {
        line, err = reader.ReadString('\n')

        fmt.Printf(" > Read %d characters\n", len(line))

        // Process the line here.
        fmt.Println(" > > " + limitLength(line, 50))

        if err != nil {
            break
        }
    }

    if err != io.EOF {
        fmt.Printf(" > Failed!: %v\n", err)
    }

    return
}

func readFileWithScanner(fn string) (err error) {
    fmt.Println("readFileWithScanner - this will fail!")

    // Don't use this, it doesn't work with long lines...

    file, err := os.Open(fn)
    defer file.Close()

    if err != nil {
        return err
    }

    // Start reading from the file using a scanner.
    scanner := bufio.NewScanner(file)

    for scanner.Scan() {
        line := scanner.Text()

        fmt.Printf(" > Read %d characters\n", len(line))

        // Process the line here.
        fmt.Println(" > > " + limitLength(line, 50))
    }

    if scanner.Err() != nil {
        fmt.Printf(" > Failed!: %v\n", scanner.Err())
    }

    return
}

func readFileWithReadLine(fn string) (err error) {
    fmt.Println("readFileWithReadLine")

    file, err := os.Open(fn)
    defer file.Close()

    if err != nil {
        return err
    }

    // Start reading from the file with a reader.
    reader := bufio.NewReader(file)

    for {
        var buffer bytes.Buffer

        var l []byte
        var isPrefix bool
        for {
            l, isPrefix, err = reader.ReadLine()
            buffer.Write(l)

            // If we've reached the end of the line, stop reading.
            if !isPrefix {
                break
            }

            // If we're just at the EOF, break
            if err != nil {
                break
            }
        }

        if err == io.EOF {
            break
        }

        line := buffer.String()

        fmt.Printf(" > Read %d characters\n", len(line))

        // Process the line here.
        fmt.Println(" > > " + limitLength(line, 50))
    }

    if err != io.EOF {
        fmt.Printf(" > Failed!: %v\n", err)
    }

    return
}

func main() {
    testLongLines()
    testLinesThatDoNotFinishWithALinebreak()
}

func testLongLines() {
    fmt.Println("Long lines")
    fmt.Println()

    createFileWithLongLine("longline.txt")
    readFileWithReadString("longline.txt")
    fmt.Println()
    readFileWithScanner("longline.txt")
    fmt.Println()
    readFileWithReadLine("longline.txt")
    fmt.Println()
}

func testLinesThatDoNotFinishWithALinebreak() {
    fmt.Println("No linebreak")
    fmt.Println()

    createFileThatDoesNotEndWithALineBreak("nolinebreak.txt")
    readFileWithReadString("nolinebreak.txt")
    fmt.Println()
    readFileWithScanner("nolinebreak.txt")
    fmt.Println()
    readFileWithReadLine("nolinebreak.txt")
    fmt.Println()
}

func createFileThatDoesNotEndWithALineBreak(fn string) (err error) {
    file, err := os.Create(fn)
    defer file.Close()

    if err != nil {
        return err
    }

    w := bufio.NewWriter(file)
    w.WriteString("Does not end with linebreak.")
    w.Flush()

    return
}

func createFileWithLongLine(fn string) (err error) {
    file, err := os.Create(fn)
    defer file.Close()

    if err != nil {
        return err
    }

    w := bufio.NewWriter(file)

    fs := 1024 * 1024 * 4 // 4MB

    // Create a 4MB long line consisting of the letter a.
    for i := 0; i < fs; i++ {
        w.WriteRune('a')
    }

    // Terminate the line with a break.
    w.WriteRune('\n')

    // Put in a second line, which doesn't have a linebreak.
    w.WriteString("Second line.")

    w.Flush()

    return
}

func limitLength(s string, length int) string {
    if len(s) < length {
        return s
    }

    return s[:length]
}
Run Code Online (Sandbox Code Playgroud)

我测试过:

  • 去版本go1.7 windows/amd64
  • 去版本go1.6.3 linux/amd64
  • 去版本go1.7.4 darwin/amd64

测试程序输出:

Long lines

readFileWithReadString
 > Read 4194305 characters
 > > aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
 > Read 12 characters
 > > Second line.

readFileWithScanner - this will fail!
 > Failed!: bufio.Scanner: token too long

readFileWithReadLine
 > Read 4194304 characters
 > > aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
 > Read 12 characters
 > > Second line.

No linebreak

readFileWithReadString
 > Read 28 characters
 > > Does not end with linebreak.

readFileWithScanner - this will fail!
 > Read 28 characters
 > > Does not end with linebreak.

readFileWithReadLine
 > Read 28 characters
 > > Does not end with linebreak.
Run Code Online (Sandbox Code Playgroud)

  • `defer file.Close()`应该在错误检查之后; 否则出错将导致恐慌. (6认同)

Mal*_*olm 51

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

我写了一种方法来轻松读取文件中的每一行.Readln(*bufio.Reader)函数从底层的bufio.Reader结构返回一行(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
}
Run Code Online (Sandbox Code Playgroud)

您可以使用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)
}
Run Code Online (Sandbox Code Playgroud)

干杯!

  • 在Go 1.1问世之前我写了这个答案.Go 1.1在stdlib中有一个Scanner软件包.它提供与我的答案相同的功能.我建议使用Scanner而不是我的答案,因为Scanner在stdlib中.快乐的黑客!:-) (14认同)

zou*_*ing 25

有两种常见的逐行读取文件的方法.

  1. 使用bufio.Scanner
  2. 在bufio.Reader中使用ReadString/ReadBytes/....

在我的测试用例中,~250MB,~2,500,000行,bufio.Scanner(使用时间: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
    }
}
Run Code Online (Sandbox Code Playgroud)

读取文件使用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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,如果文件不以换行符结尾,此“bufio.Reader”示例将不会读取文件中的最后一行。在这种情况下,“ReadString”将返回最后一行和“io.EOF”。 (2认同)

lza*_*zap 10

您还可以使用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
    }
  }
Run Code Online (Sandbox Code Playgroud)


Kok*_*zzu 9

Example from this 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
  }
}
Run Code Online (Sandbox Code Playgroud)

but this gives an error when there is a line that larger than Scanner's buffer.

When that happened, what I do is use reader := bufio.NewReader(inFile) create and concat my own buffer either using ch, err := reader.ReadByte() or len, err := reader.Read(myBuffer)


小智 8

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

import (
    "io/ioutil"
    "strings"
)

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

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

  • 对于大文件来说,像这样将整个文件读入内存然后将其分解可能会非常昂贵。 (8认同)
  • `os.ReadFile()` 似乎做了同样的事情。 (2认同)

kro*_*sse 6

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