逐行阅读文件
Go无法findfile.ReadLine
函数。 我可以想出如何快速写一个,但只是想知道我是否在这里忽略了一些东西。 如何逐行读取文件?
包bufio
有ReadLine函数。
请注意,如果该行不符合读取缓冲区,该函数将返回一个不完整的行。 如果你想总是在你的程序中通过一个函数来调用整行代码,你需要将ReadLine
函数封装到自己的函数中,该函数在for循环中调用ReadLine
函数。
bufio.ReadString('\n')
并不完全等同于ReadLine
因为当文件的最后一行不以换行符结尾时, ReadString
无法处理这种情况。
在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) } }
这是一个逐行阅读的最简洁的方式。
有一个警告:扫描仪不能处理超过65536个字符的行。 如果这对你是一个问题,那么你应该在Reader.Read()
之上推出自己的。
编辑:作为go1.1,惯用的解决scheme是使用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) }
干杯!
使用:
-
reader.ReadString('\n')
- 如果你不介意该行可能很长(即使用大量的RAM)。 它将
\n
保留在返回string的末尾。
- 如果你不介意该行可能很长(即使用大量的RAM)。 它将
-
reader.ReadLine()
- 如果你关心限制内存消耗,并且不介意处理行数大于读缓冲区大小的情况。
我通过编写程序testing了各种解决scheme,以testing在其他答案中确定为问题的情况:
- 带有4MB行的文件。
- 不以换行符结尾的文件。
我find:
-
Scanner
解决scheme不处理长行。 -
ReadLine
解决scheme实施起来很复杂。 -
ReadString
解决scheme是最简单的,适用于长线。
这里是演示每个解决scheme的代码,它可以通过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] }
我testing了:
- 去版本go1.7 windows / amd64
- 去版本go1.6.3 linux / amd64
- 去版本go1.7.4达尔文/ amd64
testing程序输出:
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.
您也可以使用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 } }
从这个要点的例子
func readLine(path string) { inFile, err := os.Open(path) if err != nil { fmt.Println(err.Error() + `: ` + path) return } else { defer inFile.Close() } scanner := bufio.NewScanner(inFile) scanner.Split(bufio.ScanLines) for scanner.Scan() { fmt.Println(scanner.Text()) // the line } }
但是当存在比Scanner的缓冲区更大的行时会产生错误。
当发生这种情况时,我所做的是使用reader := bufio.NewReader(inFile)
使用ch, err := reader.ReadByte()
或len, err := reader.Read(myBuffer)
创build并连接自己的缓冲区。
bufio.Reader.ReadLine()工作正常。 但是,如果你想通过一个string读取每一行,尝试使用ReadString('\ n') 。 它不需要重新发明轮子。
// 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 }
在下面的代码中,我从CLI中读取了感兴趣的内容,直到用户input并使用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)
我喜欢Lzap的解决scheme,我是新的在去,我想问问lzap,但我不能做到这一点,我还没有50分..我改变了一点你的解决scheme,并完成代码…
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 } }
我不知道为什么我需要再次testing'err',但无论如何我们可以做到这一点。 但是,主要的问题是,为什么Go不会在循环内部产生错误=>行,err:= r.ReadString(10)? 每次执行循环时都会一次又一次地定义它。 我避免了这种情况,我的改变,有什么意见? 我在'for'中设置条件EOF类似于一个while。 谢谢