Go'da bir dosyayı satır satır okuma


334

file.ReadLineGo işlevini bulamıyorum . Nasıl hızlı bir şekilde yazacağımı anlayabilirim, ama burada bir şeye bakıp bakmadığımı merak ediyorum. Dosya nasıl satır satır okunur?


7
Go1.1'den itibaren bufio.Scanner bunu yapmanın en iyi yoludur.
Malcolm

Yanıtlar:


133

NOT: Kabul edilen cevap Go'nun ilk sürümlerinde doğruydu. En yüksek oyu alan cevabın bunu başarmanın daha yeni deyimsel yolunu içerdiğini görün .

Pakette ReadLine işlevi vardır bufio.

Satır okuma arabelleğine sığmazsa, işlevin eksik bir satır döndüreceğini lütfen unutmayın. Bir fonksiyona tek bir çağrı yaparak programınızdaki tüm bir satırı her zaman okumak istiyorsanız, ReadLinefonksiyonu ReadLinebir for-loop'u çağıran kendi fonksiyonunuza dahil etmeniz gerekir .

bufio.ReadString('\n')tam olarak eşdeğer değildir, ReadLineçünkü ReadStringbir dosyanın son satırı yeni satır karakteri ile bitmediğinde durumu işleyemez.


37
Dokümanlardan: "ReadLine düşük seviyeli bir satır okuma ilkelidir. Çoğu arayanın yerine ReadBytes ('\ n') veya ReadString ('\ n') veya bir Tarayıcı kullanmalıdır."
mdwhatcott

12
@mdwhatcott "düşük seviyeli satır okuma ilkeli" olmasının neden önemi var? Bu, "Çoğu arayanın ReadBytes ('\ n') veya ReadString ('\ n') kullanması veya bir Tarayıcı kullanması" sonucuna nasıl ulaşır?
Charlie Parker

12
@CharlieParker - Emin değilim, sadece içerik eklemek için dokümanlardan alıntı yapıyoruz.
mdwhatcott

11
Aynı dokümanlardan. "ReadString, bir sınırlayıcı bulmadan önce bir hatayla karşılaşırsa, hatadan ve hatanın kendisinden önce okunan verileri döndürür (genellikle io.EOF)." Böylece sadece io.EOF hatasını kontrol edebilir ve tamamlandığını bilirsiniz.
eduncan911

1
Bir okuma veya yazma işleminin kesintiye uğramış bir sistem çağrısı nedeniyle başarısız olabileceğini ve bu da beklenen sayıda baytın okunmasını veya yazılmasını sağlar.
Justin Swanhart

599

Go 1.1 ve daha yeni sürümlerde bunu yapmanın en basit yolu a bufio.Scanner. Bir dosyadan satırları okuyan basit bir örnek:

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

Bu Readersatır satır okumanın en temiz yoludur .

Bir uyarı var: Tarayıcı 65536 karakterden uzun satırlarla iyi başa çıkmıyor. Bu sizin için bir sorunsa, muhtemelen kendi üstüne dönmelisiniz Reader.Read().


40
Ve OP bir dosyayı taramak istediğinden, önce file, _ := os.Open("/path/to/file.csv")dosya taraması ve ardından dosya tanıtıcısını taramak önemsiz olacaktır :scanner := bufio.NewScanner(file)
Evan Plumlee

14
Unutma defer file.Close().
Kiril

13
Sorun Scanner.Scan () satır başına 4096 [] bayt arabellek boyutu ile sınırlıdır. bufio.ErrTooLongHata alırsınız , yani bufio.Scanner: token too longçizgi çok uzunsa. Bu durumda, bufio.ReaderLine () veya ReadString () kullanmanız gerekir.
eduncan911

5
Sadece benim 0.02 $ - Bu sayfadaki en doğru cevap :)
sethvargo

5
Tarayıcı'yı Buffer () yöntemini kullanarak daha da uzun satırları işleyecek şekilde yapılandırabilirsiniz: golang.org/pkg/bufio/#Scanner.Buffer
Alex Robinson

78

kullanın:

  • reader.ReadString('\n')
    • Hattın çok uzun olabileceğini düşünmezseniz (yani çok fazla RAM kullanın). \nDöndürülen dizenin sonunda tutar .
  • reader.ReadLine()
    • RAM tüketimini sınırlamayı önemsiyorsanız ve hattın okuyucunun arabellek boyutundan daha büyük olduğu durumun ele alınmasıyla ilgili fazladan çalışmayı önemsemiyorsanız.

Diğer cevaplarda sorun olarak tanımlanan senaryoları test etmek için bir program yazarak önerilen çeşitli çözümleri test ettim:

  • 4MB satırı olan bir dosya.
  • Satır sonu ile bitmeyen bir dosya.

Onu buldum:

  • ScannerSolüsyon uzun satırları işlemez.
  • ReadLineÇözümü uygulamak için karmaşıktır.
  • ReadStringÇözüm basit olanıdır ve uzun hatlar için çalışır.

İşte her çözümü gösteren kod, üzerinden çalıştırılabilir 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]
}

Şunu test ettim:

  • go sürümü go1.7 windows / amd64
  • go sürümü go1.6.3 linux / amd64
  • go sürümü go1.7.4 darwin / amd64

Test programı çıktıları:

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
defer file.Close()Hata kontrolü sonra olmalıdır; aksi halde hata durumunda panik olur.
mlg

Tarayıcı çözümü, bu şekilde yapılandırırsanız uzun satırları işler. Bakınız: golang.org/pkg/bufio/#Scanner.Buffer
İnanç Gümüş

Hatayı dokümanlarda görüldüğü gibi düzgün bir şekilde kontrol etmelisiniz: play.golang.org/p/5CCPzVTSj6 yani err == io.EOF {break} else {return err}
Chuque

53

EDIT: go1.1 itibariyle, deyimsel çözüm bufio kullanmaktır .

Her satırı bir dosyadan kolayca okumak için bir yol yazdım. Readln (* bufio.Reader) işlevi, temeldeki bufio.Reader yapısından bir satır (sans \ n) döndürür.

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

Bir dosyadaki her satırı okumak için Readln komutunu kullanabilirsiniz. Aşağıdaki kod bir dosyadaki her satırı okur ve her satırı stdout'a çıkarır.

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

Şerefe!


14
Go 1.1 çıkmadan önce bu cevabı yazdım. Go 1.1, stdlib'de bir Tarayıcı paketine sahiptir. bu benim cevabımla aynı işlevselliği sağlıyor. Tarayıcı stdlib olduğu için cevabım yerine Tarayıcı kullanmanızı tavsiye ederim. Mutlu hack! :-)
Malcolm

30

Dosyayı satır satır okumanın iki yaygın yolu vardır.

  1. Bufio.Scanner kullanın
  2. Bufio'da ReadString / ReadBytes / ... kullanın.

Benim testcase, In ~ 250MB, ~ 2,500,000 hatları , bufio.Scanner (zaman kullanılır: 0.395491384s) (: 0.446867622s time_used) hızlı bufio.Reader.ReadString fazla.

Kaynak kodu: https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line

Dosyayı okuyun 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
    }
}

Okuma dosyası bufio.Reader kullanın,

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

Yeni bufio.Readersatırla bitmezse, bu örneğin bir dosyadaki son satırı okumayacağını unutmayın. ReadStringhem son satırı hem io.EOFde bu durumda geri döner .
konrad

18

Bu özden örnek

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

ancak bu, Scanner'ın arabelleğinden daha büyük bir çizgi olduğunda bir hata verir.

Bu olduğunda, yaptığım şey, reader := bufio.NewReader(inFile)ya kendi tamponumu ya kullanarak ch, err := reader.ReadByte()veyalen, err := reader.Read(myBuffer)

Kullandığım başka bir yol (os.Stdin'i yukarıdaki gibi bir dosyayla değiştirin), bu satırlar uzun olduğunda (isPrefix) sona erer ve boş satırları yok sayar:


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
}

nedenini açıklamak ister -1misin?
Kokizzu

Bence bu çözüm biraz karmaşıktı, değil mi?
Decebal

10

Ayrıcı olarak \ n ile ReadString'i de kullanabilirsiniz:

  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

Aşağıdaki kodda, kullanıcı girdi isabet ve ben Readline kullanıyorum kadar CLI çıkarlarını okuyun:

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

Lzap çözümünü seviyorum, Go'da yeniyim, lzap istemek istiyorum ama yapamadım henüz 50 puanım yok .. Çözümünüzü biraz değiştirip kodu tamamlıyorum ...

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

Neden 'err' i tekrar test etmem gerektiğinden emin değilim, ama yine de yapabiliriz. Ama asıl soru şu: Go neden döngü içinde => satırı, err: = r.ReadString (10) ile hata üretmiyor? Döngü her yürütüldüğünde tekrar tekrar tanımlanır. Değişikliğimden dolayı bu durumdan kaçınırım, herhangi bir yorum? 'For' da EOF koşulunu bir While'a benzer şekilde ayarladım. Teşekkürler


0
import (
     "bufio"
     "os"
)

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

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

İşte işlevi ReadFromStdin()gibi bir örnek fmt.Scan(&name)ama boş dizeleri olan tüm dizeleri alır: "Merhaba benim adım ..."

var name string = ReadFromStdin()

println(name)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.