Não consigo encontrar a file.ReadLinefunção no Go. Eu posso descobrir como escrever um rapidamente, mas estou me perguntando se estou ignorando algo aqui. Como se lê um arquivo linha por linha?
A partir do Go1.1, o bufio.Scanner é a melhor maneira de fazer isso.
Malcolm
Respostas:
133
NOTA: A resposta aceita estava correta nas versões anteriores do Go. Veja a resposta mais votada contém a maneira idiomática mais recente de conseguir isso.
Observe que, se a linha não couber no buffer de leitura, a função retornará uma linha incompleta. Se você sempre quiser ler uma linha inteira no seu programa com uma única chamada para uma função, precisará encapsular a ReadLinefunção na sua própria função, que chama ReadLineem um loop for.
bufio.ReadString('\n')não é totalmente equivalente a, ReadLineporque ReadStringé incapaz de lidar com o caso em que a última linha de um arquivo não termina com o caractere de nova linha.
Dos documentos: "ReadLine é uma primitiva de leitura de linha de baixo nível. A maioria dos chamadores deve usar ReadBytes ('\ n') ou ReadString ('\ n') ou usar um scanner."
mdwhatcott
12
@mdwhatcott por que importa que é uma "primitiva de leitura de linha de baixo nível"? Como isso chega à conclusão de que "a maioria dos chamadores deve usar ReadBytes ('\ n') ou ReadString ('\ n') ou usar um scanner."?
Charlie Parker
12
@CharlieParker - Não tenho certeza, apenas citando os documentos para adicionar contexto.
precisa saber é o seguinte
11
Dos mesmos documentos .. "Se o ReadString encontrar um erro antes de localizar um delimitador, ele retornará os dados lidos antes do erro e o próprio erro (geralmente io.EOF)." Então você pode apenas verificar o erro io.EOF e saber que você está pronto.
eduncan911
1
Observe que uma leitura ou gravação pode falhar devido a uma chamada do sistema interrompida, o que resulta em menos do que o número esperado de bytes sendo lido ou gravado.
Justin Swanhart
599
No Go 1.1 e mais recente, a maneira mais simples de fazer isso é com um bufio.Scanner. Aqui está um exemplo simples que lê linhas de um arquivo:
Essa é a maneira mais limpa de ler uma Readerlinha por linha.
Há uma ressalva: o scanner não lida bem com linhas com mais de 65536 caracteres. Se isso é um problema para você, então você provavelmente deve rolar por conta própria Reader.Read().
E já que o OP pediu para digitalizar sobre um arquivo, seria trivial para primeiro file, _ := os.Open("/path/to/file.csv")e depois digitalizar sobre o identificador de arquivo:scanner := bufio.NewScanner(file)
Evan Plumlee
14
Não esqueça disso defer file.Close().
Kiril
13
O problema é Scanner.Scan () é limitado em um tamanho de buffer de 4096 [] bytes por linha. Você receberá um bufio.ErrTooLongerro, ou seja, bufio.Scanner: token too longse a linha for muito longa. Nesse caso, você precisará usar bufio.ReaderLine () ou ReadString ().
precisa saber é o seguinte
5
Just my $ 0,02 - esta é a resposta correta mais na página :)
Se você não se importa que a linha possa ser muito longa (por exemplo, use muita memória RAM). Ele mantém o \nfinal da string retornado.
reader.ReadLine()
Se você se preocupa em limitar o consumo de RAM e não se importa com o trabalho extra de lidar com o caso em que a linha é maior que o tamanho do buffer do leitor.
Testei as várias soluções sugeridas escrevendo um programa para testar os cenários identificados como problemas em outras respostas:
Um arquivo com uma linha de 4 MB.
Um arquivo que não termina com uma quebra de linha.
Eu achei aquilo:
o Scanner solução não lida com longas filas.
A ReadLinesolução é complexa de implementar.
A ReadStringsolução é a mais simples e funciona para longas filas.
Aqui está o código que demonstra cada solução, que pode ser executado 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]}
Eu testei em:
go version go1.7 windows / amd64
go version go1.6.3 linux / amd64
go version go1.7.4 darwin / amd64
O programa de teste gera:
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.
Você deve verificar o erro corretamente como visto nos docs: play.golang.org/p/5CCPzVTSj6 ou seja, se err == io.EOF {pausa} else {err retorno}
Chuque
53
EDIT: A partir do go1.1, a solução idiomática é usar o bufio.Scanner
Eu escrevi uma maneira de ler facilmente cada linha de um arquivo. A função Readln (* bufio.Reader) retorna uma linha (sans \ n) da estrutura subjacente do 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...)}returnstring(ln),err
}
Você pode usar o Readln para ler todas as linhas de um arquivo. O código a seguir lê todas as linhas de um arquivo e gera cada linha no 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)}
Eu escrevi esta resposta antes do Go 1.1 ser lançado. O Go 1.1 possui um pacote de scanner no stdlib. que fornece a mesma funcionalidade da minha resposta. Eu recomendaria usar o Scanner em vez da minha resposta, pois o Scanner está no stdlib. Feliz hacking! :-)
Malcolm
30
Existem duas maneiras comuns de ler o arquivo linha por linha.
Use o bufio.Scanner
Use ReadString / ReadBytes / ... no bufio.Reader
No meu caso de teste, ~ 250MB, ~ 2.500.000 linhas , o bufio.Scanner (tempo usado: 0.395491384s) é mais rápido que o bufio.Reader.ReadString (time_used: 0.446867622s).
Esteja ciente de que este bufio.Readerexemplo não lerá a última linha de um arquivo se não terminar com uma nova linha. ReadStringretornará a última linha e io.EOF, neste caso.
mas isso gera um erro quando há uma linha maior que o buffer do scanner.
Quando isso aconteceu, o que faço é reader := bufio.NewReader(inFile)criar e concatenar meu próprio buffer, usando ch, err := reader.ReadByte()oulen, err := reader.Read(myBuffer)
Outra maneira que eu uso (substitua os.Stdin pelo arquivo como acima), este concata quando as linhas são longas (isPrefix) e ignora as linhas vazias:
// 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}
Gosto da solução Lzap, sou novo no Go, gostaria de pedir para o lzap, mas não consegui fazê-lo ainda não tenho 50 pontos .. Troco um pouco a sua solução e complete o 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}}
Não sei por que preciso testar 'err' novamente, mas de qualquer maneira podemos fazê-lo. Mas, a questão principal é .. por que o Go não produz erros com a frase => line, err: = r.ReadString (10), dentro do loop? É definido repetidamente toda vez que o loop é executado. Evito essa situação com a minha mudança, algum comentário? Defino a condição EOF em 'for' como semelhante a um While também. obrigado
Aqui está um exemplo com a função ReadFromStdin()que é como, fmt.Scan(&name)mas recebe todas as strings com espaços em branco como: "Olá, meu nome é ..."
Outro método é usar as bibliotecas io/ioutile stringspara ler os bytes do arquivo inteiro, convertê-los em uma sequência e dividi-los usando um caractere " \n" (nova linha) como delimitador, por exemplo:
Tecnicamente, você não está lendo o arquivo linha por linha, mas é possível analisar cada linha usando essa técnica. Este método é aplicável a arquivos menores. Se você estiver tentando analisar um arquivo enorme, use uma das técnicas que lê linha por linha.
Respostas:
NOTA: A resposta aceita estava correta nas versões anteriores do Go. Veja a resposta mais votada contém a maneira idiomática mais recente de conseguir isso.
Existe a função ReadLine no pacote
bufio
.Observe que, se a linha não couber no buffer de leitura, a função retornará uma linha incompleta. Se você sempre quiser ler uma linha inteira no seu programa com uma única chamada para uma função, precisará encapsular a
ReadLine
função na sua própria função, que chamaReadLine
em um loop for.bufio.ReadString('\n')
não é totalmente equivalente a,ReadLine
porqueReadString
é incapaz de lidar com o caso em que a última linha de um arquivo não termina com o caractere de nova linha.fonte
No Go 1.1 e mais recente, a maneira mais simples de fazer isso é com um
bufio.Scanner
. Aqui está um exemplo simples que lê linhas de um arquivo:Essa é a maneira mais limpa de ler uma
Reader
linha por linha.Há uma ressalva: o scanner não lida bem com linhas com mais de 65536 caracteres. Se isso é um problema para você, então você provavelmente deve rolar por conta própria
Reader.Read()
.fonte
file, _ := os.Open("/path/to/file.csv")
e depois digitalizar sobre o identificador de arquivo:scanner := bufio.NewScanner(file)
defer file.Close()
.bufio.ErrTooLong
erro, ou seja,bufio.Scanner: token too long
se a linha for muito longa. Nesse caso, você precisará usar bufio.ReaderLine () ou ReadString ().Usar:
reader.ReadString('\n')
\n
final da string retornado.reader.ReadLine()
Testei as várias soluções sugeridas escrevendo um programa para testar os cenários identificados como problemas em outras respostas:
Eu achei aquilo:
Scanner
solução não lida com longas filas.ReadLine
solução é complexa de implementar.ReadString
solução é a mais simples e funciona para longas filas.Aqui está o código que demonstra cada solução, que pode ser executado via
go run main.go
:Eu testei em:
O programa de teste gera:
fonte
defer file.Close()
deve ser após a verificação de erro; caso contrário, por erro, entrará em pânico.EDIT: A partir do go1.1, a solução idiomática é usar o bufio.Scanner
Eu escrevi uma maneira de ler facilmente cada linha de um arquivo. A função Readln (* bufio.Reader) retorna uma linha (sans \ n) da estrutura subjacente do bufio.Reader.
Você pode usar o Readln para ler todas as linhas de um arquivo. O código a seguir lê todas as linhas de um arquivo e gera cada linha no stdout.
Felicidades!
fonte
Existem duas maneiras comuns de ler o arquivo linha por linha.
No meu caso de teste, ~ 250MB, ~ 2.500.000 linhas , o bufio.Scanner (tempo usado: 0.395491384s) é mais rápido que o bufio.Reader.ReadString (time_used: 0.446867622s).
Código fonte: https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line
Leia o arquivo usando bufio.Scanner,
Leia o arquivo usando bufio.Reader,
fonte
bufio.Reader
exemplo não lerá a última linha de um arquivo se não terminar com uma nova linha.ReadString
retornará a última linha eio.EOF
, neste caso.Exemplo desta essência
mas isso gera um erro quando há uma linha maior que o buffer do scanner.
Quando isso aconteceu, o que faço é
reader := bufio.NewReader(inFile)
criar e concatenar meu próprio buffer, usandoch, err := reader.ReadByte()
oulen, err := reader.Read(myBuffer)
Outra maneira que eu uso (substitua os.Stdin pelo arquivo como acima), este concata quando as linhas são longas (isPrefix) e ignora as linhas vazias:
fonte
-1
?Você também pode usar o ReadString com \ n como um separador:
fonte
bufio.Reader.ReadLine () funciona bem. Mas se você quiser ler cada linha por uma string, tente usar ReadString ('\ n') . Não precisa reinventar a roda.
fonte
fonte
No código abaixo, leio os interesses da CLI até que o usuário aperte enter e estou usando o Readline:
fonte
Gosto da solução Lzap, sou novo no Go, gostaria de pedir para o lzap, mas não consegui fazê-lo ainda não tenho 50 pontos .. Troco um pouco a sua solução e complete o código ...
Não sei por que preciso testar 'err' novamente, mas de qualquer maneira podemos fazê-lo. Mas, a questão principal é .. por que o Go não produz erros com a frase => line, err: = r.ReadString (10), dentro do loop? É definido repetidamente toda vez que o loop é executado. Evito essa situação com a minha mudança, algum comentário? Defino a condição EOF em 'for' como semelhante a um While também. obrigado
fonte
Aqui está um exemplo com a função
ReadFromStdin()
que é como,fmt.Scan(&name)
mas recebe todas as strings com espaços em branco como: "Olá, meu nome é ..."fonte
Outro método é usar as bibliotecas
io/ioutil
estrings
para ler os bytes do arquivo inteiro, convertê-los em uma sequência e dividi-los usando um caractere "\n
" (nova linha) como delimitador, por exemplo:Tecnicamente, você não está lendo o arquivo linha por linha, mas é possível analisar cada linha usando essa técnica. Este método é aplicável a arquivos menores. Se você estiver tentando analisar um arquivo enorme, use uma das técnicas que lê linha por linha.
fonte