la lecture du fichier ligne par ligne en aller

Je suis incapable de trouver la fonction file.ReadLine dans Go. Je peux comprendre comment écrire rapidement un, mais je me demande si je néglige quelque chose ici. Comment lire un fichier ligne par ligne?

208
demandé sur Greg Dubicki 2012-01-06 15:50:24

12 réponses

Il y a la fonction ReadLine dans le paquet bufio.

Veuillez noter que si la ligne ne rentre pas dans le tampon de lecture, la fonction renverra une ligne incomplète. Si vous voulez toujours de lire une ligne entière dans votre programme par un seul appel à une fonction, vous aurez besoin d'encapsuler le ReadLine fonction dans votre propre fonction qui appelle ReadLine dans une boucle for.

bufio.ReadString('\n') n'est pas entièrement équivalent à ReadLine Car ReadString ne peut pas gérer le cas lorsque la dernière ligne d'un fichier ne se termine pas par le caractère de retour à la ligne.

82
répondu Flimzy 2015-12-29 10:51:36

, Allez 1.1 et plus récente de la façon la plus simple de le faire est avec un bufio.Scanner. Voici un exemple simple qui lit les lignes d'un fichier:

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

C'est la façon la plus propre de le lire à partir d'un Reader ligne par ligne.

Il y a une mise en garde: Scanner ne traite pas bien les lignes de plus de 65536 caractères. Si c'est un problème pour vous, alors vous devriez probablement rouler le vôtre sur Reader.Read().

408
répondu Stefan Arentz 2015-10-14 08:51:10

EDIT: à partir de go1.1, la solution idiomatique consiste à utiliser bufio.Scanner

J'ai écrit un moyen de lire facilement chaque ligne à partir d'un fichier. Le Readln (*bufio.La fonction Reader) renvoie une ligne (sans \n) du bufio sous-jacent.Lecteur de struct.

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

Vous pouvez utiliser Readln pour lire chaque ligne d'un fichier. Le code suivant lit chaque ligne dans un fichier et sort chaque ligne vers 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)
}

Cheers!

44
répondu Malcolm 2014-08-07 22:08:17

Utilisation:

  • reader.ReadString('\n')
    • si cela ne vous dérange pas que la ligne puisse être très longue (c'est-à-dire utiliser beaucoup de RAM). Il conserve le \n à la fin de la chaîne retournée.
  • reader.ReadLine()
    • Si vous vous souciez de limiter la consommation de RAM et que cela ne vous dérange pas le travail supplémentaire de gérer le cas où la ligne est supérieure à la taille de la mémoire tampon du lecteur.

J'ai testé les différentes solutions proposées en écrivant un programme pour tester les scénarios qui sont identifiés comme des problèmes dans d'autres réponses:

  • un fichier avec une ligne de 4 Mo.
  • un fichier qui ne se termine pas par un saut de ligne.

, j'ai trouvé que:

  • la solution Scanner ne gère pas les longues lignes.
  • La solution ReadLine est complexe à mettre en œuvre.
  • la solution ReadString est la plus simple et fonctionne pour les longues lignes.

Voici le code qui démontre chaque solution, il peut être exécuté via 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]
}

J'ai testé sur:

  • Go version go1. 7 windows / amd64
  • Go version go1.6. 3 linux / amd64
  • Go version go1.7. 4 darwin / amd64

Les sorties du programme de test:

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.
39
répondu a-h 2017-05-22 19:53:13

Il y a deux façons courantes de lire le fichier ligne par ligne.

  1. Utilisez bufio.Scanner
  2. Utilisez ReadString / ReadBytes/... a bufio.Lecteur

Dans mon testcase, ~ 250MB, ~ 2,500,000 lignes, bufio.Scanner (temps utilisé: 0.395491384 s) est plus rapide que bufio.Lecteur.ReadString(time_used: 0.446867622 s).

Code Source: https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line

Lire le fichier utiliser 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
    }
}

Lire le fichier utilisez bufio.Lecteur,

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
    }
}
11
répondu zouying 2018-09-14 02:16:35

Vous pouvez également utiliser ReadString avec \n comme séparateur:

  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
    }
  }
8
répondu lzap 2012-12-02 21:11:50

Exemple de cette 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
  }
}

Mais cela donne une erreur quand il y a une ligne plus grande que le tampon du Scanner.

Lorsque cela est arrivé, ce que je fais est d'utiliser reader := bufio.NewReader(inFile) Créer et concattre mon propre tampon soit en utilisant ch, err := reader.ReadByte() ou len, err := reader.Read(myBuffer)

6
répondu Kokizzu 2016-02-25 15:18:20

Bufio.Lecteur.ReadLine () fonctionne bien. Mais si vous voulez lire chaque ligne par une chaîne, essayez d'utiliser ReadString ('\n') . Il n'a pas besoin de réinventer la roue.

4
répondu kroisse 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                                                   
}                                    
3
répondu cyber 2013-05-10 06:15:50

Dans le code ci-dessous, je lis les intérêts de la CLI jusqu'à ce que l'utilisateur frappe entrée et j'utilise 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)
1
répondu zuzuleinen 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
}

Voici un exemple avec la fonction ReadFromStdin() c'est comme fmt.Scan(&name) mais il prend toutes les chaînes avec des espaces vides comme: "bonjour mon nom est ..."

var name string = ReadFromStdin()

println(name)
1
répondu 0DAYanc 2017-12-18 09:45:58

J'aime la solution Lzap, je suis nouveau dans Go, Je voudrais demander à lzap mais je ne pouvais pas le faire, je n'ai pas encore 50 points.. Je change un peu votre solution et complète le code...

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

Je ne sais pas pourquoi j'ai besoin de tester 'err' à nouveau, mais de toute façon nous pouvons le faire. Mais, la question principale est.. pourquoi aller ne pas produire d'erreur avec la phrase = > ligne, err: = R. ReadString(10), à l'intérieur de la boucle ? Il est défini encore et encore chaque fois que la boucle est exécutée. J'évite cette situation avec mon changer, un commentaire? J'ai mis la condition EOF dans le 'for' comme similaire à un certain temps aussi. Merci

0
répondu Jose.m.g 2016-03-02 22:39:46