go中逐行读取文件

时间:2012-01-06 11:50:25

标签: string file parsing go line

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

14 个答案:

答案 0 :(得分:487)

在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)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

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

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

之上自行推出

答案 1 :(得分:104)

bufio中有功能ReadLine

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

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

答案 2 :(得分:57)

使用:

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

我测试了:

  • go go go7.7 windows / amd64
  • go go go.6.6.3 linux / amd64
  • go go go7.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.

答案 3 :(得分:48)

编辑:从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
}

您可以使用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)
}

干杯!

答案 4 :(得分:18)

有两种常用的逐行读取文件的方法。

  1. 使用bufio.Scanner
  2. 在bufio.Reader
  3. 中使用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
        }
    }
    

    读取文件使用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
        }
    }
    

答案 5 :(得分:8)

您也可以使用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
    }
  }

答案 6 :(得分:6)

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)

连接我自己的缓冲区

答案 7 :(得分:4)

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

答案 8 :(得分:3)

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

答案 9 :(得分:1)

在下面的代码中,我从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)

答案 10 :(得分:1)

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)类似,但它的所有字符串都带有空格,例如:&#34; Hello My Name is ...&#34;

var name string = ReadFromStdin()

println(name)

答案 11 :(得分:1)

另一种方法是使用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 :(得分:1)

在新版本的Go 1.16中,我们可以使用包嵌入来读取文件内容,如下所示。

# dummy data:
array1 = np.random.randint(10, size=10)
array2 = np.random.randint(10, size=15)

grid = sns.displot([array1, array2], kind="kde")
lg = grid.fig.legends[0] 
lg.texts[0].set_text('ZERO')
lg.texts[1].set_text('ONE')

有关更多详细信息,请访问https://tip.golang.org/pkg/embed/https://golangtutorial.dev/tips/embed-files-in-go/

答案 13 :(得分:0)

我喜欢Lzap解决方案,我是Go的新手, 我想问lzap但是我不能这样做我还没有50分...... 我改变了一点你的解决方案并完成了代码...

package main

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

func main() {
    f, err := os.Open("archiveName")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    defer f.Close()
    r := bufio.NewReader(f)
    line, err := r.ReadString(10)    // line defined once 
    for err != io.EOF {
        fmt.Print(line)              // or any stuff
        line, err = r.ReadString(10) //  line was defined before
    }
}

我不确定为什么我需要再次测试'错误',但无论如何我们都可以做到。 但是,主要问题是...... 为什么围棋不会产生错误 =&GT; line,err:= r.ReadString(10),在循环中? 每次执行循环时都会一次又一次地定义它。 我的变化,任何评论都避免了这种情况? 我将'for'中的条件EOF设置为类似于While。 感谢