lectura de archivos línea por línea en go
No puedo encontrar la función file.ReadLine
en Go. Puedo averiguar cómo escribir rápidamente uno, pero me pregunto si estoy pasando por alto algo aquí. ¿Cómo se lee un archivo línea por línea?
12 answers
Existe la función ReadLine en el paquete bufio
.
Tenga en cuenta que si la línea no encaja en el búfer de lectura, la función devolverá una línea incompleta. Si desea leer siempre una línea completa en su programa mediante una sola llamada a una función, necesitará encapsular la función ReadLine
en su propia función que llama a ReadLine
en un bucle for.
bufio.ReadString('\n')
no es completamente equivalente a ReadLine
porque ReadString
no puede manejar el caso cuando la última línea de un archivo no termina con el carácter de nueva línea.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-29 10:51:36
En Go 1.1 y más reciente la forma más sencilla de hacer esto es con un bufio.Scanner
. Aquí hay un ejemplo simple que lee líneas de un archivo:
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)
}
}
Esta es la forma más limpia de leer de una Reader
línea por línea.
Hay una advertencia: Scanner no trata bien con líneas de más de 65536 caracteres. Si eso es un problema para usted, entonces probablemente debería rodar su propio encima de Reader.Read()
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-14 08:51:10
EDITAR: A partir de go1.1, la solución idiomática es usar bufio.Escáner
Escribí una manera de leer fácilmente cada línea de un archivo. The Readln (*bufio.Reader) devuelve una línea (sans \n) del bufio subyacente.Estructura del lector.
// 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
}
Puede usar Readln para leer cada línea de un archivo. El siguiente código lee cada línea en un archivo y envía cada línea a 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)
}
Salud!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-07 22:08:17
Uso:
-
reader.ReadString('\n')
- Si no le importa que la línea podría ser muy larga (es decir, usar mucha RAM). Mantiene el
\n
al final de la cadena devuelta.
- Si no le importa que la línea podría ser muy larga (es decir, usar mucha RAM). Mantiene el
-
reader.ReadLine()
- Si le importa limitar el consumo de RAM y no le importa el trabajo adicional de manejar el caso donde la línea es mayor que el tamaño del búfer del lector.
Probé las diversas soluciones sugeridas escribiendo un programa para probar los escenarios que se identifican como problemas en otras respuestas:
- Un archivo con una línea de 4MB.
- Un archivo que no termina con un salto de línea.
Encontré que:
- La solución
Scanner
no maneja líneas largas. - La solución
ReadLine
es compleja de implementar. - La solución
ReadString
es la más simple y funciona para largas filas.
Aquí está el código que demuestra cada solución, se puede ejecutar a través de 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]
}
He probado on:
- go versión go1. 7 windows/amd64
- go versión go1. 6. 3 linux / amd64
- go versión go1. 7. 4 darwin / amd64
Las salidas del programa de prueba:
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.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-22 19:53:13
Hay dos formas comunes de leer archivo línea por línea.
- Use bufio.Escáner
- Use ReadString/ReadBytes/... en bufio.Reader
En mi caso de prueba, ~250MB, ~2,500,000 líneas, bufio.El escáner (tiempo utilizado: 0.395491384 s) es más rápido que bufio.Lector.ReadString (time_used: 0.446867622 s).
Código Fuente: https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line
Archivo de lectura use bufio.Escáner,
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
}
}
Leer archivo uso bufio.Lector,
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
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-14 02:16:35
También puede usar ReadString con \n como separador:
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
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-02 21:11:50
Ejemplo de esto gist
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
}
}
Pero esto da un error cuando hay una línea que es más grande que el búfer del escáner.
Cuando eso sucedió, lo que hago es usar reader := bufio.NewReader(inFile)
crear y concat mi propio búfer usando ch, err := reader.ReadByte()
o len, err := reader.Read(myBuffer)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-25 15:18:20
Bufio.Lector.ReadLine () funciona bien. Pero si quieres leer cada línea por una cadena, intenta usar ReadString('\n'). No necesita reinventar la rueda.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-20 06:07:39
// 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
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-10 06:15:50
En el siguiente código, leo los intereses de la CLI hasta que el usuario pulsa enter y estoy usando 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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-22 21:20:38
import (
"bufio"
"os"
)
var (
reader = bufio.NewReader(os.Stdin)
)
func ReadFromStdin() string{
result, _ := reader.ReadString('\n')
witl := result[:len(result)-1]
return witl
}
Aquí hay un ejemplo con la función ReadFromStdin()
es como fmt.Scan(&name)
pero toma todas las cadenas con espacios en blanco como: "Hello My Name Is ..."
var name string = ReadFromStdin()
println(name)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-18 09:45:58
Me gusta la solución Lzap, soy nuevo en Go, Me gustaría pedir a lzap pero no pude hacerlo No tengo 50 puntos todavía.. Cambio un poco tu solución y completo el código...
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
}
}
No estoy seguro de por qué necesito probar 'err' de nuevo, pero de todos modos podemos hacerlo. Pero, la pregunta principal es.. ¿por qué Ir no producir error con la oración = > line, err: = r. ReadString (10), inside the loop ? Se define una y otra vez cada vez que se ejecuta el bucle. Evito esa situación con mi cambio, ¿algún comentario? Establezco la condición EOF en el' para ' como similar a un tiempo también. Gracias
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-02 22:39:46