Leggere un file riga per riga in Vai


335

Non riesco a trovare la file.ReadLinefunzione in Go. Posso capire come scriverne uno velocemente, ma mi chiedo solo se sto trascurando qualcosa qui. Come si legge un file riga per riga?


7
A partire da Go1.1, bufio.Scanner è il modo migliore per farlo.
Malcolm,

Risposte:


133

NOTA: la risposta accettata era corretta nelle prime versioni di Go. Vedere la risposta più votata contiene il modo idiomatico più recente per raggiungere questo obiettivo.

C'è la funzione ReadLine nel pacchetto bufio.

Si noti che se la riga non si adatta al buffer di lettura, la funzione restituirà una riga incompleta. Se vuoi sempre leggere un'intera riga del tuo programma con una singola chiamata a una funzione, dovrai incapsulare la ReadLinefunzione nella tua funzione che chiama ReadLinein un ciclo for.

bufio.ReadString('\n')non è del tutto equivalente ReadLineperché ReadStringnon è in grado di gestire il caso in cui l'ultima riga di un file non termina con il carattere di nuova riga.


37
Dai documenti: "ReadLine è una primitiva di lettura di riga di basso livello. La maggior parte dei chiamanti dovrebbe invece utilizzare ReadBytes ('\ n') o ReadString ('\ n') o utilizzare uno scanner."
mdwhatcott,

12
@mdwhatcott perché è importante che si tratti di una "primitiva di lettura di righe di basso livello"? In che modo arriva alla conclusione che "La maggior parte dei chiamanti dovrebbe invece utilizzare ReadBytes ('\ n') o ReadString ('\ n') o utilizzare uno scanner."?
Charlie Parker,

12
@CharlieParker - Non sono sicuro, sto solo citando i documenti per aggiungere contesto.
mdwhatcott,

11
Dagli stessi documenti. "Se ReadString rileva un errore prima di trovare un delimitatore, restituisce i dati letti prima dell'errore e l'errore stesso (spesso io.EOF)." Quindi puoi semplicemente controllare l'errore io.EOF e sapere che hai finito.
eduncan911,

1
Si noti che una lettura o una scrittura possono non riuscire a causa di una chiamata di sistema interrotta, che comporta la lettura o la scrittura di un numero di byte inferiore al previsto.
Justin Swanhart,

599

In Go 1.1 e versioni successive il modo più semplice per farlo è con a bufio.Scanner. Ecco un semplice esempio che legge le righe da un file:

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

Questo è il modo più pulito di leggere Readerriga per riga.

C'è un avvertimento: lo scanner non gestisce bene le righe più lunghe di 65536 caratteri. Se questo è un problema per te, allora dovresti probabilmente farlo da solo Reader.Read().


40
E poiché l'OP ha chiesto di scansionare un file, sarebbe prima banale file, _ := os.Open("/path/to/file.csv")e poi scansionare l'handle del file:scanner := bufio.NewScanner(file)
Evan Plumlee,

14
Non dimenticare di defer file.Close().
Kiril

13
Il problema è Scanner.Scan () è limitato con una dimensione del buffer di 4096 [] byte per riga. Verrà visualizzato un bufio.ErrTooLongerrore, ovvero bufio.Scanner: token too longse la linea è troppo lunga. In tal caso, dovrai utilizzare bufio.ReaderLine () o ReadString ().
eduncan911,

5
Solo i miei $ 0,02 - questa è la risposta più corretta sulla pagina :)
sethvargo,

5
Puoi configurare Scanner per gestire linee ancora più lunghe usando il suo metodo Buffer (): golang.org/pkg/bufio/#Scanner.Buffer
Alex Robinson

78

Uso:

  • reader.ReadString('\n')
    • Se non ti dispiace che la linea potrebbe essere molto lunga (cioè usare molta RAM). Mantiene la \nfine della stringa restituita.
  • reader.ReadLine()
    • Se ti interessa limitare il consumo di RAM e non ti preoccupare del lavoro extra di gestione del caso in cui la linea è maggiore della dimensione del buffer del lettore.

Ho testato le varie soluzioni suggerite scrivendo un programma per testare gli scenari identificati come problemi in altre risposte:

  • Un file con una linea da 4 MB.
  • Un file che non termina con un'interruzione di riga.

L'ho trovato:

  • La Scannersoluzione non gestisce le linee lunghe.
  • La ReadLinesoluzione è complessa da implementare.
  • La ReadStringsoluzione è la più semplice e funziona per le lunghe file.

Ecco il codice che dimostra ogni soluzione, può essere eseguito tramite 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]
}

Ho testato su:

  • vai versione go1.7 windows / amd64
  • vai versione go1.6.3 linux / amd64
  • versione go go 1.7.4 darwin / amd64

Il programma di test genera:

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.

9
L' defer file.Close()dovrebbe essere dopo il controllo degli errori; altrimenti in caso di errore sarà prendere dal panico.
mlg

La soluzione scanner gestisce le linee lunghe se la si configura in questo modo. Vedi: golang.org/pkg/bufio/#Scanner.Buffer
Gumus

Dovresti controllare correttamente l'errore come visto nei documenti: play.golang.org/p/5CCPzVTSj6 cioè se err == io.EOF {break} else {return err}
Chuque

53

EDIT: A partire da go1.1, la soluzione idiomatica è usare bufio.Scanner

Ho scritto un modo per leggere facilmente ogni riga da un file. La funzione Readln (* bufio.Reader) restituisce una riga (sans \ n) dalla struttura bufio.Reader sottostante.

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

Puoi usare Readln per leggere ogni riga di un file. Il codice seguente legge ogni riga di un file e invia ciascuna riga 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)
}

Saluti!


14
Ho scritto questa risposta prima che uscisse Go 1.1. Go 1.1 ha un pacchetto scanner in stdlib. che fornisce le stesse funzionalità della mia risposta. Consiglierei di usare Scanner invece della mia risposta poiché Scanner è nella stdlib. Happy hacking! :-)
Malcolm,

30

Esistono due modi comuni per leggere il file riga per riga.

  1. Usa bufio.Scanner
  2. Usa ReadString / ReadBytes / ... in bufio.Reader

Nel mio testcase, ~ 250 MB, ~ 2.500.000 righe , bufio.Scanner (tempo utilizzato: 0.395491384s) è più veloce di bufio.Reader.ReadString (time_used: 0.446867622s).

Codice sorgente: https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line

Leggi il file usa 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
    }
}

Leggi il file usa 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
    }
}

Tenere presente che questo bufio.Readeresempio non leggerà l'ultima riga di un file se non termina con una nuova riga. ReadStringrestituirà sia l'ultima riga che io.EOFin questo caso.
Konrad,

18

Esempio da questa sintesi

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

ma questo dà un errore quando c'è una linea più grande del buffer dello scanner.

Quando ciò accade, ciò che faccio è usare reader := bufio.NewReader(inFile)creare e concaticare il mio buffer usando ch, err := reader.ReadByte()olen, err := reader.Read(myBuffer)

Un altro modo che uso (sostituisco os.Stdin con file come sopra), questo concatena quando le linee sono lunghe (isPrefix) e ignora le linee vuote:


func readLines() []string {
  r := bufio.NewReader(os.Stdin)
  bytes := []byte{}
  lines := []string{}
  for {
    line, isPrefix, err := r.ReadLine()
    if err != nil {
      break
    }
    bytes = append(bytes, line...)
    if !isPrefix {
      str := strings.TrimSpace(string(bytes))
      if len(str) > 0 {
        lines = append(lines, str)
        bytes = []byte{}
      }
    }
  }
  if len(bytes) > 0 {
    lines = append(lines, string(bytes))
  }
  return lines
}

ti interessa spiegare perché -1?
Kokizzu,

Penso che sia un po 'complicata questa soluzione, vero?
Decebal,

10

Puoi anche utilizzare ReadString con \ n come separatore:

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


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                                                   
}                                    

1

Nel codice seguente, ho letto gli interessi dalla CLI fino a quando l'utente non preme invio e sto 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)

0

Mi piace la soluzione Lzap, sono nuovo in Go, mi piacerebbe chiedere a Lzap ma non ci sono riuscito Non ho ancora 50 punti .. Cambio un po 'la tua soluzione e compilo il codice ...

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

Non sono sicuro del motivo per cui devo testare nuovamente "err", ma in ogni caso possiamo farlo. Ma la domanda principale è ... perché Go non produce errori con la frase => linea, err: = r.ReadString (10), all'interno del ciclo? Viene definito più volte ogni volta che viene eseguito il loop. Evito quella situazione con il mio cambiamento, qualche commento? Ho impostato la condizione EOF in 'for' come simile a While. Grazie


0
import (
     "bufio"
     "os"
)

var (
    reader = bufio.NewReader(os.Stdin)
)

func ReadFromStdin() string{
    result, _ := reader.ReadString('\n')
    witl := result[:len(result)-1]
    return witl
}

Ecco un esempio con la funzione ReadFromStdin()è come fmt.Scan(&name)ma prende tutte le stringhe con spazi vuoti come: "Ciao, il mio nome è ..."

var name string = ReadFromStdin()

println(name)

0

Un altro metodo consiste nell'utilizzare le librerie io/ioutile stringsper leggere i byte dell'intero file, convertirli in una stringa e dividerli utilizzando un carattere " \n" (newline) come delimitatore, ad esempio:

import (
    "io/ioutil"
    "strings"
)

func main() {
    bytesRead, _ := ioutil.ReadFile("something.txt")
    file_content := string(bytesRead)
    lines := strings.Split(file_content, "\n")
}

Tecnicamente non stai leggendo il file riga per riga, tuttavia puoi analizzare ogni riga usando questa tecnica. Questo metodo è applicabile a file più piccoli. Se stai tentando di analizzare un file di grandi dimensioni, utilizza una delle tecniche che legge riga per riga.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.