leyendo archivo línea por línea en go

No puedo encontrar la función file.ReadLine en Go. Puedo descubrir cómo escribir uno rápidamente, pero me pregunto si estoy pasando por alto algo aquí. ¿Cómo se lee un archivo línea por línea?

Hay una función ReadLine en el paquete bufio .

Tenga en cuenta que si la línea no cabe en el búfer de lectura, la función devolverá una línea incompleta. Si desea leer siempre una línea completa en su progtwig mediante una sola llamada a una función, deberá encapsular la función ReadLine en su propia función que llama a ReadLine en un bucle for.

bufio.ReadString('\n') no es totalmente 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.

En Go 1.1 y bufio.Scanner posteriores, la forma más sencilla de hacerlo 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 un Reader línea por línea.

Hay una advertencia: el escáner no funciona bien con líneas de más de 65536 caracteres. Si eso es un problema para ti, entonces probablemente deberías hacer tu propio sobre Reader.Read() .

EDITAR: A partir de go1.1, la solución idiomática es usar bufio.Scanner

Escribí una forma de leer fácilmente cada línea de un archivo. La función Readln (* bufio.Reader) devuelve una línea (sans \ n) de la estructura subyacente bufio.Reader.

 // 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 genera cada línea en 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) } 

¡Aclamaciones!

Utilizar:

  • reader.ReadString('\n')
    • Si no te importa, la línea podría ser muy larga (es decir, usar mucha RAM). Mantiene \n al final de la cadena devuelta.
  • reader.ReadLine()
    • Si le importa limitar el consumo de RAM y no le molesta el trabajo extra de manejar el caso donde la línea es mayor que el tamaño del búfer del lector.

Probé las diversas soluciones sugeridas al escribir un progtwig 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.

Encontre eso:

  • La solución de 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 líneas largas.

Aquí hay un 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] } 

Probé en:

  • ir versión go1.7 windows / amd64
  • ir versión go1.6.3 linux / amd64
  • ir versión go1.7.4 darwin / amd64

Los resultados del progtwig 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. 

Hay dos formas comunes de leer el archivo línea por línea.

  1. Utilice bufio.Scanner
  2. Use ReadString / ReadBytes / … en bufio.Reader

En mi caso de prueba, ~ 250MB, ~ 2,500,000 líneas , bufio.Scanner (tiempo utilizado: 0.395491384s) es más rápido que bufio.Reader.ReadString (time_used: 0.446867622s).

Código fuente: https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line

El uso del archivo de lectura 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() } if err := sc.Err(); err != nil { log.Fatalf("scan file error: %v", err) return } } 

Lea el archivo use 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 } } 

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

Ejemplo de esta esencia

 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 buffer del Scanner.

Cuando eso sucedió, lo que hago es usar reader := bufio.NewReader(inFile) crear y concaturar mi propio buffer usando ch, err := reader.ReadByte() o len, err := reader.Read(myBuffer)

bufio.Reader.ReadLine () funciona bien. Pero si quiere leer cada línea con una cadena, intente utilizar ReadString (‘\ n’) . No necesita reinventar la rueda.

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

En el siguiente código, leo los intereses de la CLI hasta que el usuario acceda a ingresar 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) 
 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: “Hola, mi nombre es …”

 var name string = ReadFromStdin() println(name) 

Me gusta la solución de Lzap, soy nuevo en Ir, me gustaría preguntarle a lzap pero no pude hacerlo. Aún no tengo 50 puntos. Cambié un poco su solución y complete 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é tengo que volver a probar “errar”, pero de todos modos podemos hacerlo. Pero, la pregunta principal es … ¿por qué Go no produce error con la oración => line, err: = r.ReadString (10), dentro del ciclo? Se define una y otra vez cada vez que se ejecuta el ciclo. Evito esa situación con mi cambio, ¿algún comentario? Establecí la condición EOF en ‘for’ como similar a While. Gracias