Ich kann keine file.ReadLineFunktion in Go finden. Ich kann herausfinden, wie man schnell einen schreibt, aber ich frage mich nur, ob ich hier etwas übersehen habe. Wie liest man eine Datei Zeile für Zeile?
Ab Go1.1 ist bufio.Scanner der beste Weg, dies zu tun.
Malcolm
Antworten:
133
HINWEIS: Die akzeptierte Antwort war in früheren Versionen von Go korrekt. Die Antwort mit der höchsten Stimmenzahl enthält den neueren idiomatischen Weg, um dies zu erreichen.
Bitte beachten Sie, dass die Funktion eine unvollständige Zeile zurückgibt, wenn die Zeile nicht in den Lesepuffer passt. Wenn Sie immer eine ganze Zeile in Ihrem Programm durch einen einzelnen Aufruf einer Funktion lesen möchten, müssen Sie die ReadLineFunktion in Ihre eigene Funktion kapseln, die ReadLinein einer for-Schleife aufruft .
bufio.ReadString('\n')ist nicht vollständig äquivalent zu, ReadLineda ReadStringder Fall nicht behandelt werden kann, wenn die letzte Zeile einer Datei nicht mit dem Zeilenumbruchzeichen endet.
Aus den Dokumenten: "ReadLine ist ein Grundelement zum Lesen von Zeilen auf niedriger Ebene. Die meisten Anrufer sollten stattdessen ReadBytes ('\ n') oder ReadString ('\ n') oder einen Scanner verwenden."
Mdwhatcott
12
@mdwhatcott warum ist es wichtig, dass es ein "Low-Level-Zeilenlese-Primitiv" ist? Wie kommt man zu dem Schluss, dass "die meisten Anrufer stattdessen ReadBytes ('\ n') oder ReadString ('\ n') oder einen Scanner verwenden sollten"?
Charlie Parker
12
@CharlieParker - Nicht sicher, nur die Dokumente zitieren, um Kontext hinzuzufügen.
Mdwhatcott
11
Aus denselben Dokumenten. "Wenn ReadString vor dem Auffinden eines Trennzeichens auf einen Fehler stößt, werden die vor dem Fehler gelesenen Daten und der Fehler selbst (häufig io.EOF) zurückgegeben." Sie können also einfach nach io.EOF-Fehlern suchen und wissen, dass Sie fertig sind.
eduncan911
1
Beachten Sie, dass ein Lese- oder Schreibvorgang aufgrund eines unterbrochenen Systemaufrufs fehlschlagen kann, was dazu führt, dass weniger als die erwartete Anzahl von Bytes gelesen oder geschrieben wird.
Justin Swanhart
597
In Go 1.1 und höher ist der einfachste Weg, dies zu tun, mit a bufio.Scanner. Hier ist ein einfaches Beispiel, das Zeilen aus einer Datei liest:
Dies ist die sauberste Art, ReaderZeile für Zeile zu lesen .
Es gibt eine Einschränkung: Der Scanner kann mit Zeilen, die länger als 65536 Zeichen sind, nicht gut umgehen. Wenn das ein Problem für Sie ist, sollten Sie wahrscheinlich Ihr eigenes darüber rollen Reader.Read().
Und da das OP darum gebeten hat, eine Datei zu scannen, wäre es trivial, zuerst das Dateihandle zu scannen file, _ := os.Open("/path/to/file.csv"):scanner := bufio.NewScanner(file)
Evan Plumlee
14
Vergiss es nicht defer file.Close().
Kiril
13
Das Problem ist, dass Scanner.Scan () auf eine Puffergröße von 4096 [] Byte pro Zeile begrenzt ist. Sie erhalten eine bufio.ErrTooLongFehlermeldung, bufio.Scanner: token too longwenn die Leitung zu lang ist. In diesem Fall müssen Sie bufio.ReaderLine () oder ReadString () verwenden.
eduncan911
5
Nur meine $ 0.02 - das ist die richtigste Antwort auf der Seite :)
Sethvargo
5
Sie können den Scanner so konfigurieren, dass er mit der Buffer () -Methode noch längere Zeilen verarbeitet: golang.org/pkg/bufio/#Scanner.Buffer
Alex Robinson
77
Verwenden:
reader.ReadString('\n')
Wenn Sie nichts dagegen haben, dass die Leitung sehr lang sein kann (dh viel RAM verwenden). Es behält die \nam Ende der Zeichenfolge zurückgegebene.
reader.ReadLine()
Wenn Sie den RAM-Verbrauch begrenzen möchten und sich nicht um die zusätzliche Arbeit kümmern müssen, wenn die Zeile größer als die Puffergröße des Lesers ist.
Ich habe die verschiedenen Lösungsvorschläge getestet, indem ich ein Programm geschrieben habe, um die Szenarien zu testen, die in anderen Antworten als Probleme identifiziert werden:
Eine Datei mit einer 4-MB-Zeile.
Eine Datei, die nicht mit einem Zeilenumbruch endet.
Ich habe das gefunden:
Die ScannerLösung verarbeitet keine langen Schlangen.
Die ReadLineLösung ist komplex zu implementieren.
Die ReadStringLösung ist die einfachste und funktioniert für lange Schlangen.
Hier ist Code, der jede Lösung demonstriert und über Folgendes ausgeführt werden kann 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]}
Ich habe getestet an:
go version go1.7 windows / amd64
go version go1.6.3 linux / amd64
go version go1.7.4 darwin / amd64
Das Testprogramm gibt aus:
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.
Sie sollten den Fehler ordnungsgemäß überprüfen, wie in den Dokumenten gezeigt: play.golang.org/p/5CCPzVTSj6 dh dh wenn err == io.EOF {break} else {return err}
Chuque
53
EDIT: Ab go1.1 besteht die idiomatische Lösung darin, bufio.Scanner zu verwenden
Ich habe eine Möglichkeit geschrieben, jede Zeile einfach aus einer Datei zu lesen. Die Funktion Readln (* bufio.Reader) gibt eine Zeile (sans \ n) von der zugrunde liegenden Struktur bufio.Reader zurück.
// 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
}
Mit Readln können Sie jede Zeile aus einer Datei lesen. Der folgende Code liest jede Zeile in einer Datei und gibt jede Zeile an stdout aus.
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)}
Ich habe diese Antwort geschrieben, bevor Go 1.1 herauskam. Go 1.1 hat ein Scanner-Paket in der stdlib. das bietet die gleiche Funktionalität wie meine Antwort. Ich würde empfehlen, Scanner anstelle meiner Antwort zu verwenden, da sich Scanner in der stdlib befindet. Viel Spaß beim Hacken! :-)
Malcolm
30
Es gibt zwei gängige Methoden, um Dateien Zeile für Zeile zu lesen.
Verwenden Sie bufio.Scanner
Verwenden Sie ReadString / ReadBytes / ... in bufio.Reader
In meinem Testfall ist bufio.Scanner (verwendete Zeit: 0,395491384s) mit ~ 250 MB, ~ 2.500.000 Zeilen schneller als bufio.Reader.ReadString (time_used: 0,446867622s).
Beachten Sie, dass in diesem bufio.ReaderBeispiel die letzte Zeile in einer Datei nicht gelesen wird, wenn sie nicht mit einer neuen Zeile endet. ReadStringgibt sowohl die letzte Zeile als auch io.EOFin diesem Fall zurück.
Dies führt jedoch zu einem Fehler, wenn eine Zeile größer als der Puffer des Scanners ist.
Wenn das passiert ist reader := bufio.NewReader(inFile), benutze ich create und concat meinen eigenen Puffer entweder mit ch, err := reader.ReadByte()oderlen, err := reader.Read(myBuffer)
Eine andere Art, die ich benutze (os.Stdin durch Datei wie oben ersetzen), ist diese, wenn lange Zeilen lang sind (isPrefix) und leere Zeilen ignoriert:
// 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}
Ich mag die Lzap-Lösung, ich bin neu in Go, ich würde gerne nach lzap fragen, aber ich konnte es nicht tun. Ich habe noch keine 50 Punkte. Ich ändere ein wenig Ihre Lösung und vervollständige den 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}}
Ich bin mir nicht sicher, warum ich 'err' erneut testen muss, aber wir können es trotzdem tun. Die Hauptfrage ist jedoch, warum Go keinen Fehler mit dem Satz => line, err: = r.ReadString (10) innerhalb der Schleife erzeugt. Sie wird bei jeder Ausführung der Schleife immer wieder definiert. Ich vermeide diese Situation mit meiner Änderung, irgendeinen Kommentar? Ich habe die Bedingung EOF im 'für' ähnlich wie bei einer Weile eingestellt. Vielen Dank
Hier ist ein Beispiel mit einer Funktion ReadFromStdin(), wie sie ist, fmt.Scan(&name)aber alle Zeichenfolgen mit Leerzeichen wie "Hallo, mein Name ist ..."
Antworten:
HINWEIS: Die akzeptierte Antwort war in früheren Versionen von Go korrekt. Die Antwort mit der höchsten Stimmenzahl enthält den neueren idiomatischen Weg, um dies zu erreichen.
Es gibt die Funktion ReadLine im Paket
bufio
.Bitte beachten Sie, dass die Funktion eine unvollständige Zeile zurückgibt, wenn die Zeile nicht in den Lesepuffer passt. Wenn Sie immer eine ganze Zeile in Ihrem Programm durch einen einzelnen Aufruf einer Funktion lesen möchten, müssen Sie die
ReadLine
Funktion in Ihre eigene Funktion kapseln, dieReadLine
in einer for-Schleife aufruft .bufio.ReadString('\n')
ist nicht vollständig äquivalent zu,ReadLine
daReadString
der Fall nicht behandelt werden kann, wenn die letzte Zeile einer Datei nicht mit dem Zeilenumbruchzeichen endet.quelle
In Go 1.1 und höher ist der einfachste Weg, dies zu tun, mit a
bufio.Scanner
. Hier ist ein einfaches Beispiel, das Zeilen aus einer Datei liest:Dies ist die sauberste Art,
Reader
Zeile für Zeile zu lesen .Es gibt eine Einschränkung: Der Scanner kann mit Zeilen, die länger als 65536 Zeichen sind, nicht gut umgehen. Wenn das ein Problem für Sie ist, sollten Sie wahrscheinlich Ihr eigenes darüber rollen
Reader.Read()
.quelle
file, _ := os.Open("/path/to/file.csv")
:scanner := bufio.NewScanner(file)
defer file.Close()
.bufio.ErrTooLong
Fehlermeldung,bufio.Scanner: token too long
wenn die Leitung zu lang ist. In diesem Fall müssen Sie bufio.ReaderLine () oder ReadString () verwenden.Verwenden:
reader.ReadString('\n')
\n
am Ende der Zeichenfolge zurückgegebene.reader.ReadLine()
Ich habe die verschiedenen Lösungsvorschläge getestet, indem ich ein Programm geschrieben habe, um die Szenarien zu testen, die in anderen Antworten als Probleme identifiziert werden:
Ich habe das gefunden:
Scanner
Lösung verarbeitet keine langen Schlangen.ReadLine
Lösung ist komplex zu implementieren.ReadString
Lösung ist die einfachste und funktioniert für lange Schlangen.Hier ist Code, der jede Lösung demonstriert und über Folgendes ausgeführt werden kann
go run main.go
:Ich habe getestet an:
Das Testprogramm gibt aus:
quelle
defer file.Close()
sollte nach der Fehlerprüfung sein; Andernfalls gerät es bei einem Fehler in Panik.EDIT: Ab go1.1 besteht die idiomatische Lösung darin, bufio.Scanner zu verwenden
Ich habe eine Möglichkeit geschrieben, jede Zeile einfach aus einer Datei zu lesen. Die Funktion Readln (* bufio.Reader) gibt eine Zeile (sans \ n) von der zugrunde liegenden Struktur bufio.Reader zurück.
Mit Readln können Sie jede Zeile aus einer Datei lesen. Der folgende Code liest jede Zeile in einer Datei und gibt jede Zeile an stdout aus.
Prost!
quelle
Es gibt zwei gängige Methoden, um Dateien Zeile für Zeile zu lesen.
In meinem Testfall ist bufio.Scanner (verwendete Zeit: 0,395491384s) mit ~ 250 MB, ~ 2.500.000 Zeilen schneller als bufio.Reader.ReadString (time_used: 0,446867622s).
Quellcode: https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line
Datei lesen mit bufio.Scanner,
Datei lesen use bufio.Reader,
quelle
bufio.Reader
Beispiel die letzte Zeile in einer Datei nicht gelesen wird, wenn sie nicht mit einer neuen Zeile endet.ReadString
gibt sowohl die letzte Zeile als auchio.EOF
in diesem Fall zurück.Beispiel aus diesem Kern
Dies führt jedoch zu einem Fehler, wenn eine Zeile größer als der Puffer des Scanners ist.
Wenn das passiert ist
reader := bufio.NewReader(inFile)
, benutze ich create und concat meinen eigenen Puffer entweder mitch, err := reader.ReadByte()
oderlen, err := reader.Read(myBuffer)
Eine andere Art, die ich benutze (os.Stdin durch Datei wie oben ersetzen), ist diese, wenn lange Zeilen lang sind (isPrefix) und leere Zeilen ignoriert:
quelle
-1
du erklären warum ?Sie können ReadString auch mit \ n als Trennzeichen verwenden:
quelle
bufio.Reader.ReadLine () funktioniert gut. Wenn Sie jedoch jede Zeile mit einer Zeichenfolge lesen möchten, versuchen Sie, ReadString ('\ n') zu verwenden . Das Rad muss nicht neu erfunden werden.
quelle
quelle
Im folgenden Code lese ich die Interessen aus der CLI, bis der Benutzer die Eingabetaste drückt und ich Readline verwende:
quelle
Ich mag die Lzap-Lösung, ich bin neu in Go, ich würde gerne nach lzap fragen, aber ich konnte es nicht tun. Ich habe noch keine 50 Punkte. Ich ändere ein wenig Ihre Lösung und vervollständige den Code ...
Ich bin mir nicht sicher, warum ich 'err' erneut testen muss, aber wir können es trotzdem tun. Die Hauptfrage ist jedoch, warum Go keinen Fehler mit dem Satz => line, err: = r.ReadString (10) innerhalb der Schleife erzeugt. Sie wird bei jeder Ausführung der Schleife immer wieder definiert. Ich vermeide diese Situation mit meiner Änderung, irgendeinen Kommentar? Ich habe die Bedingung EOF im 'für' ähnlich wie bei einer Weile eingestellt. Vielen Dank
quelle
Hier ist ein Beispiel mit einer Funktion
ReadFromStdin()
, wie sie ist,fmt.Scan(&name)
aber alle Zeichenfolgen mit Leerzeichen wie "Hallo, mein Name ist ..."quelle