Je n'arrive pas à trouver de file.ReadLinefonction dans Go. Je peux comprendre comment en écrire rapidement un, mais je me demande simplement si j'oublie quelque chose ici. Comment lire un fichier ligne par ligne?
Depuis Go1.1, bufio.Scanner est le meilleur moyen de le faire.
Malcolm
Réponses:
133
REMARQUE: la réponse acceptée était correcte dans les premières versions de Go. Voir la réponse la plus votée contient la manière idiomatique la plus récente d'y parvenir.
Il y a la fonction ReadLine dans le package 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 lire une ligne entière dans votre programme par un seul appel à une fonction, vous devrez encapsuler la ReadLinefonction dans votre propre fonction qui appelle ReadLinedans une boucle for.
bufio.ReadString('\n')n'est pas entièrement équivalent à ReadLinecar ReadStringest incapable de gérer le cas lorsque la dernière ligne d'un fichier ne se termine pas par le caractère de nouvelle ligne.
D'après les documents: "ReadLine est une primitive de lecture de ligne de bas niveau. La plupart des appelants devraient utiliser ReadBytes ('\ n') ou ReadString ('\ n') à la place ou utiliser un scanner."
mdwhatcott
12
@mdwhatcott pourquoi est-ce important que ce soit une "primitive de lecture de ligne de bas niveau"? Comment cela aboutit-il à la conclusion que «la plupart des appelants devraient plutôt utiliser ReadBytes ('\ n') ou ReadString ('\ n') ou utiliser un scanner."?
Charlie Parker
12
@CharlieParker - Pas sûr, juste en citant les documents pour ajouter du contexte.
mdwhatcott
11
De la même documentation .. "Si ReadString rencontre une erreur avant de trouver un délimiteur, il renvoie les données lues avant l'erreur et l'erreur elle-même (souvent io.EOF)." Ainsi, vous pouvez simplement vérifier l'erreur io.EOF et savoir que vous avez terminé.
eduncan911
1
Notez qu'une lecture ou une écriture peut échouer en raison d'un appel système interrompu, ce qui entraîne une lecture ou une écriture inférieure au nombre d'octets attendu.
Justin Swanhart
599
Dans Go 1.1 et plus récent, le moyen le plus simple de le faire est d'utiliser a bufio.Scanner. Voici un exemple simple qui lit les lignes d'un fichier:
C'est la façon la plus propre de lire d'une Readerligne à l'autre.
Il y a une mise en garde: le scanner ne gère pas bien les lignes de plus de 65 536 caractères. Si c'est un problème pour vous, alors vous devriez probablement lancer le vôtre Reader.Read().
Et puisque l'OP a demandé de numériser un fichier, il serait trivial de d'abord file, _ := os.Open("/path/to/file.csv")puis de numériser le descripteur de fichier:scanner := bufio.NewScanner(file)
Evan Plumlee
14
N'oubliez pas defer file.Close().
Kiril
13
Le problème est Scanner.Scan () est limité dans une taille de tampon de 4096 [] octets par ligne. Vous obtiendrez une bufio.ErrTooLongerreur, c'est-à-dire bufio.Scanner: token too longsi la ligne est trop longue. Dans ce cas, vous devrez utiliser bufio.ReaderLine () ou ReadString ().
eduncan911
5
Juste mon 0,02 $ - c'est la réponse la plus correcte sur la page :)
sethvargo
5
Vous pouvez configurer Scanner pour gérer des lignes encore plus longues en utilisant sa méthode Buffer (): golang.org/pkg/bufio/#Scanner.Buffer
Alex Robinson
78
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 ne vous occupez pas du travail supplémentaire de gestion du 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 identifiés comme 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.
Je l'ai trouvé:
La Scannersolution ne gère pas les longues lignes.
La ReadLinesolution est complexe à mettre en œuvre.
La ReadStringsolution est la plus simple et fonctionne pour les longues lignes.
Voici le code qui illustre 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 stringfor{
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.Buffervar l []bytevar isPrefix boolfor{
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, breakif 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:
aller version go1.7 windows / amd64
aller version go1.6.3 linux / amd64
aller version go1.7.4 darwin / amd64
Le programme de test produit:
Long lines
readFileWithReadString
>Read4194305 characters
>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
>Read12 characters
>>Second line.
readFileWithScanner -this will fail!>Failed!: bufio.Scanner: token too long
readFileWithReadLine
>Read4194304 characters
>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
>Read12 characters
>>Second line.No linebreak
readFileWithReadString
>Read28 characters
>>Doesnotendwith linebreak.
readFileWithScanner -this will fail!>Read28 characters
>>Doesnotendwith linebreak.
readFileWithReadLine
>Read28 characters
>>Doesnotendwith linebreak.
Vous devriez vérifier l'erreur correctement comme vu dans les documents: play.golang.org/p/5CCPzVTSj6 ie if err == io.EOF {break} else {return err}
Chuque
53
EDIT: À partir de go1.1, la solution idiomatique est d'utiliser bufio.Scanner
J'ai écrit un moyen de lire facilement chaque ligne d'un fichier. La fonction Readln (* bufio.Reader) renvoie une ligne (sans \ n) à partir de la structure bufio.Reader sous-jacente.
// 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...)}returnstring(ln),err
}
Vous pouvez utiliser Readln pour lire chaque ligne d'un fichier. Le code suivant lit chaque ligne d'un fichier et envoie chaque ligne à 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)}
J'ai écrit cette réponse avant la sortie de Go 1.1. Go 1.1 a un package Scanner dans le stdlib. qui fournit les mêmes fonctionnalités que ma réponse. Je recommanderais d'utiliser Scanner au lieu de ma réponse car Scanner est dans le stdlib. Bon piratage! :-)
Malcolm
30
Il existe deux façons courantes de lire le fichier ligne par ligne.
Utilisez bufio.Scanner
Utilisez ReadString / ReadBytes / ... dans bufio.Reader
Dans mon testcase, ~ 250 Mo, ~ 2 500 000 lignes , bufio.Scanner (temps utilisé: 0,395491384s) est plus rapide que bufio.Reader.ReadString (time_used: 0,446867622s).
Sachez que cet bufio.Readerexemple ne lira pas la dernière ligne d'un fichier s'il ne se termine pas par une nouvelle ligne. ReadStringretournera à la fois la dernière ligne et io.EOFdans ce cas.
mais cela donne une erreur lorsqu'il y a une ligne plus grande que la mémoire tampon du scanner.
Lorsque cela s'est produit, ce que je fais est d'utiliser la reader := bufio.NewReader(inFile)création et la concaténation de mon propre tampon en utilisant ch, err := reader.ReadByte()oulen, err := reader.Read(myBuffer)
Une autre façon que j'utilise (remplacez os.Stdin par un fichier comme ci-dessus), celle-ci concatène lorsque les lignes sont longues (isPrefix) et ignore les lignes vides:
bufio.Reader.ReadLine () fonctionne bien. Mais si vous souhaitez lire chaque ligne par une chaîne, essayez d'utiliser ReadString ('\ n') . Il n'a pas besoin de réinventer la roue.
// 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}
J'aime la solution Lzap, je suis nouveau dans Go, j'aimerais demander à lzap mais je n'ai pas pu 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 je dois tester à nouveau 'err', mais de toute façon nous pouvons le faire. Mais, la question principale est .. pourquoi Go ne produit pas d'erreur avec la phrase => ligne, err: = r.ReadString (10), à l'intérieur de la boucle? Il est défini encore et encore à chaque exécution de la boucle. J'évite cette situation avec mon changement, un commentaire? J'ai également défini la condition EOF dans le «pour» comme pour un While. Merci
Voici un exemple avec la fonction à laquelle ReadFromStdin()il ressemble, fmt.Scan(&name)mais il prend toutes les chaînes avec des espaces vides comme: "Bonjour, mon nom est ..."
Une autre méthode consiste à utiliser les bibliothèques io/ioutilet stringspour lire l'intégralité des octets du fichier, les convertir en chaîne et les diviser en utilisant un caractère " \n" (nouvelle ligne) comme délimiteur, par exemple:
Techniquement, vous ne lisez pas le fichier ligne par ligne, mais vous pouvez analyser chaque ligne en utilisant cette technique. Cette méthode est applicable aux fichiers plus petits. Si vous essayez d'analyser un fichier volumineux, utilisez l'une des techniques de lecture ligne par ligne.
Réponses:
REMARQUE: la réponse acceptée était correcte dans les premières versions de Go. Voir la réponse la plus votée contient la manière idiomatique la plus récente d'y parvenir.
Il y a la fonction ReadLine dans le package
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 lire une ligne entière dans votre programme par un seul appel à une fonction, vous devrez encapsuler la
ReadLine
fonction dans votre propre fonction qui appelleReadLine
dans une boucle for.bufio.ReadString('\n')
n'est pas entièrement équivalent àReadLine
carReadString
est incapable de gérer le cas lorsque la dernière ligne d'un fichier ne se termine pas par le caractère de nouvelle ligne.la source
Dans Go 1.1 et plus récent, le moyen le plus simple de le faire est d'utiliser a
bufio.Scanner
. Voici un exemple simple qui lit les lignes d'un fichier:C'est la façon la plus propre de lire d'une
Reader
ligne à l'autre.Il y a une mise en garde: le scanner ne gère pas bien les lignes de plus de 65 536 caractères. Si c'est un problème pour vous, alors vous devriez probablement lancer le vôtre
Reader.Read()
.la source
file, _ := os.Open("/path/to/file.csv")
puis de numériser le descripteur de fichier:scanner := bufio.NewScanner(file)
defer file.Close()
.bufio.ErrTooLong
erreur, c'est-à-direbufio.Scanner: token too long
si la ligne est trop longue. Dans ce cas, vous devrez utiliser bufio.ReaderLine () ou ReadString ().Utilisation:
reader.ReadString('\n')
\n
à la fin de la chaîne retournée.reader.ReadLine()
J'ai testé les différentes solutions proposées en écrivant un programme pour tester les scénarios identifiés comme problèmes dans d'autres réponses:
Je l'ai trouvé:
Scanner
solution ne gère pas les longues lignes.ReadLine
solution est complexe à mettre en œuvre.ReadString
solution est la plus simple et fonctionne pour les longues lignes.Voici le code qui illustre chaque solution, il peut être exécuté via
go run main.go
:J'ai testé sur:
Le programme de test produit:
la source
defer file.Close()
devrait être après la vérification des erreurs; sinon, en cas d'erreur, cela paniquera.EDIT: À partir de go1.1, la solution idiomatique est d'utiliser bufio.Scanner
J'ai écrit un moyen de lire facilement chaque ligne d'un fichier. La fonction Readln (* bufio.Reader) renvoie une ligne (sans \ n) à partir de la structure bufio.Reader sous-jacente.
Vous pouvez utiliser Readln pour lire chaque ligne d'un fichier. Le code suivant lit chaque ligne d'un fichier et envoie chaque ligne à stdout.
À votre santé!
la source
Il existe deux façons courantes de lire le fichier ligne par ligne.
Dans mon testcase, ~ 250 Mo, ~ 2 500 000 lignes , bufio.Scanner (temps utilisé: 0,395491384s) est plus rapide que bufio.Reader.ReadString (time_used: 0,446867622s).
Code source: https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line
Lire le fichier, utilisez bufio.Scanner,
Lire le fichier utilise bufio.Reader,
la source
bufio.Reader
exemple ne lira pas la dernière ligne d'un fichier s'il ne se termine pas par une nouvelle ligne.ReadString
retournera à la fois la dernière ligne etio.EOF
dans ce cas.Exemple de cet essentiel
mais cela donne une erreur lorsqu'il y a une ligne plus grande que la mémoire tampon du scanner.
Lorsque cela s'est produit, ce que je fais est d'utiliser la
reader := bufio.NewReader(inFile)
création et la concaténation de mon propre tampon en utilisantch, err := reader.ReadByte()
oulen, err := reader.Read(myBuffer)
Une autre façon que j'utilise (remplacez os.Stdin par un fichier comme ci-dessus), celle-ci concatène lorsque les lignes sont longues (isPrefix) et ignore les lignes vides:
la source
-1
?Vous pouvez également utiliser ReadString avec \ n comme séparateur:
la source
bufio.Reader.ReadLine () fonctionne bien. Mais si vous souhaitez lire chaque ligne par une chaîne, essayez d'utiliser ReadString ('\ n') . Il n'a pas besoin de réinventer la roue.
la source
la source
Dans le code ci-dessous, je lis les intérêts de la CLI jusqu'à ce que l'utilisateur frappe et j'utilise Readline:
la source
J'aime la solution Lzap, je suis nouveau dans Go, j'aimerais demander à lzap mais je n'ai pas pu le faire je n'ai pas encore 50 points .. Je change un peu votre solution et complète le code ...
Je ne sais pas pourquoi je dois tester à nouveau 'err', mais de toute façon nous pouvons le faire. Mais, la question principale est .. pourquoi Go ne produit pas d'erreur avec la phrase => ligne, err: = r.ReadString (10), à l'intérieur de la boucle? Il est défini encore et encore à chaque exécution de la boucle. J'évite cette situation avec mon changement, un commentaire? J'ai également défini la condition EOF dans le «pour» comme pour un While. Merci
la source
Voici un exemple avec la fonction à laquelle
ReadFromStdin()
il ressemble,fmt.Scan(&name)
mais il prend toutes les chaînes avec des espaces vides comme: "Bonjour, mon nom est ..."la source
Une autre méthode consiste à utiliser les bibliothèques
io/ioutil
etstrings
pour lire l'intégralité des octets du fichier, les convertir en chaîne et les diviser en utilisant un caractère "\n
" (nouvelle ligne) comme délimiteur, par exemple:Techniquement, vous ne lisez pas le fichier ligne par ligne, mais vous pouvez analyser chaque ligne en utilisant cette technique. Cette méthode est applicable aux fichiers plus petits. Si vous essayez d'analyser un fichier volumineux, utilisez l'une des techniques de lecture ligne par ligne.
la source