Goで行ごとにファイルを読み取る


335

file.ReadLineGoで機能が見つかりません。すぐに書ける方法はわかりますが、ここで何か見落としているのではないかと思っています。ファイルを1行ずつ読み取る方法を教えてください。


7
Go1.1以降、bufio.Scannerがこれを行うための最良の方法です。
Malcolm

回答:


133

注:受け入れられた回答は、Goの初期バージョンでは正解でした。 最高投票数の回答には、これを達成するための最近の慣用的な方法が含まれています。

パッケージに関数ReadLineがありますbufio

行が読み取りバッファーに収まらない場合、関数は不完全な行を返すことに注意してください。関数の1回の呼び出しでプログラムの行全体を常に読み取る場合は、forループReadLineで呼び出す独自の関数に関数をカプセル化する必要がありReadLineます。

bufio.ReadString('\n')は、がファイルの最後の行が改行文字で終わっていない場合に対応できないReadLineため、ReadStringと完全に同等ではありません。


37
ドキュメントから:「ReadLineは低レベルの行読みプリミティブです。ほとんどの呼び出し元は、代わりにReadBytes( '\ n')またはReadString( '\ n')を使用するか、スキャナーを使用する必要があります。」
mdwhatcott 2014年

12
@mdwhatcottなぜそれが「低レベルの行読みプリミティブ」であることが重要なのですか?「ほとんどの発信者は代わりにReadBytes( '\ n')またはReadString( '\ n')を使用するか、スキャナーを使用する必要がある」という結論にどのように到達しますか?
チャーリーパーカー

12
@CharlieParker-わかりません。コンテキストを追加するためにドキュメントを引用しています。
mdwhatcott 2014

11
同じドキュメントから。「ReadStringは、区切り文字を見つける前にエラーが発生した場合、エラーの前に読み取られたデータとエラー自体(多くの場合、io.EOF)を返します。」したがって、io.EOFエラーをチェックして、完了したことを確認できます。
eduncan911 2014年

1
システムコールが中断されたために読み取りまたは書き込みが失敗する可能性があることに注意してください。その結果、読み取りまたは書き込みされるバイト数が予想より少なくなります。
ジャスティンスワンハート、

599

Go 1.1以降では、これを行う最も簡単な方法はを使用することbufio.Scannerです。次に、ファイルから行を読み取る簡単な例を示します。

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

これは、1 Reader行ずつ読み取る最もクリーンな方法です。

注意点が1つあります。スキャナーは65536文字を超える行を適切に処理しません。それがあなたにとって問題であるならば、あなたはおそらくあなた自身の上にあなた自身を転がすべきですReader.Read()


40
OPは、ファイル全体を走査するように求めので、それは最初に些細なことだろうfile, _ := os.Open("/path/to/file.csv")し、ファイルハンドルの上にスキャン:scanner := bufio.NewScanner(file)
エヴァンPlumlee

14
を忘れないでくださいdefer file.Close()
キリル2014

13
問題はScanner.Scan()が1行あたり4096 []バイトのバッファサイズに制限されていることです。行が長すぎると、bufio.ErrTooLongエラーが発生しますbufio.Scanner: token too long。その場合は、bufio.ReaderLine()またはReadString()を使用する必要があります。
eduncan911 2014年

5
ちょうど私の$
0.02-

5
Buffer()メソッドを使用してさらに長い行を処理するようにスキャナーを構成できます。golang.org
Alex Robinson

78

使用する:

  • reader.ReadString('\n')
    • 行が非常に長くなる可能性があることを気にしない場合(つまり、大量のRAMを使用する場合)。それは続けて\n返される文字列の末尾。
  • reader.ReadLine()
    • RAMの消費を制限することに関心があり、行がリーダーのバッファーサイズより大きい場合の処理​​の余分な作業を気にしない場合。

他の回答で問題として特定されたシナリオをテストするプログラムを作成することによって提案されたさまざまなソリューションをテストしました。

  • 4MB行のファイル。
  • 改行で終わらないファイル。

見つけた:

  • Scannerソリューションは、長い行を処理しません。
  • ReadLineソリューションを実装するために複雑です。
  • ReadStringソリューションは、最も簡単で、長い行のために動作します。

各ソリューションを示すコードは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]
}

私はテストしました:

  • goバージョンgo1.7 windows / amd64
  • goバージョンgo1.6.3 linux / amd64
  • goバージョンgo1.7.4 darwin / amd64

テストプログラムの出力:

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()エラーチェックした後でなければなりません。それ以外の場合は、エラー時にパニックになります。
mlg 2017

スキャナーソリューションは、このように構成すると、長い回線を処理します。参照:golang.org/pkg/bufio/#Scanner.Buffer
Inanc Gumus

あなたは、ドキュメントに見られるように、適切にエラーをチェックする必要があります:play.golang.org/p/5CCPzVTSj6 すなわちERR == io.EOF {休憩} {他に戻りERR}場合
Chuque

53

編集:go1.1以降、慣用的な解決策はbufio.Scannerを使用することです

ファイルから各行を簡単に読み取る方法を書きました。Readln(* bufio.Reader)関数は、基になるbufio.Reader構造体から行(sans \ n)を返します。

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

Readlnを使用して、ファイルからすべての行を読み取ることができます。次のコードは、ファイルのすべての行を読み取り、各行を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)
}

乾杯!


14
Go 1.1が出る前に私はこの答えを書きました。Go 1.1のstdlibにはスキャナーパッケージがあります。それは私の答えと同じ機能を提供します。Scannerはstdlibにあるため、回答の代わりにScannerを使用することをお勧めします。ハッキングハッピー!:-)
マルコム

30

ファイルを1行ずつ読み取る一般的な方法は2つあります。

  1. bufio.Scannerを使用する
  2. ReadString / ReadBytes / ...をbufio.Readerで使用します

私のテストケースでは、〜250MB、〜2,500,000行、bufio.Scanner(使用時間:0.395491384s)はbufio.Reader.ReadString(time_used:0.446867622s)より高速です。

ソースコード: https //github.com/xpzouying/go-practice/tree/master/read_file_line_by_line

ファイルを読み取るには、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
    }
}

ファイルを読み取るには、bufio.Readerを使用します。

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

このbufio.Reader例では、改行で終わっていない場合、ファイルの最後の行を読み取らないことに注意してください。ReadString最後の行とio.EOFこの場合の両方を返します。
konrad 2018年

18

この要点の

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

ただし、スキャナーのバッファーよりも大きい行があると、エラーが発生します。

それが起こったとき、私がやっていることは、reader := bufio.NewReader(inFile)createまたはconcatを使用して、ch, err := reader.ReadByte()またはlen, err := reader.Read(myBuffer)

私が使用する別の方法(上記のようにos.Stdinをファイルで置き換える)、これは行が長い(isPrefix)ときに連結し、空の行を無視します。


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
}

なぜ説明するのか-1
Kokizzu 2015

私はそれを考えます;この解決策は少し複雑すぎましたね?
Decebal 2016

10

ReadStringを\ nをセパレータとして使用することもできます。

  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

以下のコードでは、ユーザーがEnterキーを押してReadlineを使用するまで、CLIから興味を読み取ります。

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ソリューションが好きです、私はGoの新人です、私はlzapに頼むのが好きですが、それを実行できませんでした。まだ50ポイントを持っていません。

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

なぜ「err」を再度テストする必要があるのか​​はわかりませんが、とにかくそれを行うことができます。しかし、主な質問は..ループ内で=>行err:= r.ReadString(10)という文でエラーが発生しないのはなぜですか?ループが実行されるたびに何度も定義されます。私の変更で、そのような状況を回避します、コメントはありますか?「for」の条件EOFをWhileと同様に設定しました。ありがとう


0
import (
     "bufio"
     "os"
)

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

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

ここでは機能を持つ例であるReadFromStdin()、それはようなものだfmt.Scan(&name)「...こんにちは私の名前である」:しかし、そのようには空白を持つすべての文字列を取ります

var name string = ReadFromStdin()

println(name)

0

もう1つの方法は、io/ioutilおよびstringsライブラリを使用してファイルのバイト全体を読み取り、それらを文字列に変換し\n、区切り文字として" "(改行)文字を使用して分割することです。次に例を示します。

import (
    "io/ioutil"
    "strings"
)

func main() {
    bytesRead, _ := ioutil.ReadFile("something.txt")
    file_content := string(bytesRead)
    lines := strings.Split(file_content, "\n")
}

技術的にはファイルを1行ずつ読み取っていませんが、この手法を使用して各行を解析できます。この方法は、小さいファイルに適用できます。大規模なファイルを解析しようとしている場合は、1行ずつ読み取る手法のいずれかを使用してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.