Go에서 한 줄씩 파일 읽기


334

file.ReadLineGo에서 기능 을 찾을 수 없습니다 . 빠르게 작성하는 방법을 알 수는 있지만 여기서 뭔가를 간과하고 있는지 궁금합니다. 파일을 한 줄씩 읽는 방법은 무엇입니까?


7
Go1.1 현재 bufio.Scanner가 가장 좋은 방법입니다.
Malcolm

답변:


133

참고 : 허용되는 답변은 초기 버전의 Go에서 정확했습니다. 가장 높은 투표 답변을 보려면 최신 관용적 방법이 포함되어 있습니다.

package에 ReadLine 함수가 있습니다 bufio.

행이 읽기 버퍼에 맞지 않으면이 함수는 불완전한 행을 반환합니다. 함수를 한 번만 호출하여 프로그램의 전체 행을 항상 읽으려면 for 루프 ReadLine를 호출 하는 함수를 함수에 캡슐화해야합니다 ReadLine.

bufio.ReadString('\n')파일의 마지막 행이 개행 문자로 끝나지 않는 경우를 처리 할 수 ReadLine없기 때문에 완전히 동일 ReadString하지 않습니다.


37
문서에서 : "ReadLine은 저수준의 행 읽기 프리미티브입니다. 대부분의 호출자는 ReadBytes ( '\ n') 또는 ReadString ( '\ n')을 대신 사용하거나 스캐너를 사용해야합니다."
mdwhatcott

12
@mdwhatcott 왜 "저수준 라인 판독 프리미티브"가 중요합니까? "대부분의 발신자는 ReadBytes ( '\ n') 또는 ReadString ( '\ n')을 대신 사용하거나 스캐너를 사용해야합니다."라는 결론에 도달하는 방법은 무엇입니까?
Charlie Parker

12
@CharlieParker-확실하지 않습니다. 문서를 인용하여 컨텍스트를 추가하십시오.
mdwhatcott

11
같은 문서에서. "읽기 문자열을 구분 기호를 찾기 전에 오류가 발생하면 오류 전에 읽은 데이터와 오류 자체 (종종 io.EOF)를 반환합니다." 따라서 io.EOF 오류를 확인하고 완료되었음을 알 수 있습니다.
eduncan911

1
시스템 호출이 중단되어 읽기 또는 쓰기가 실패 할 수 있으며, 이로 인해 읽거나 쓰는 예상 바이트 수보다 적습니다.
저스틴 스완 하트

598

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

이것은 한 Reader줄씩 읽는 가장 깨끗한 방법 입니다.

한 가지주의 사항이 있습니다. 스캐너는 65536자를 초과하는 행을 제대로 처리하지 못합니다. 그것이 당신에게 문제라면, 아마도 당신은 위에 자신을 굴려야 할 것입니다 Reader.Read().


40
그리고 OP가 파일을 스캔하도록 요청 했으므로 file, _ := os.Open("/path/to/file.csv")파일 핸들 을 먼저 스캔 한 다음 스캔하는 것이 쉽지 않습니다 .scanner := bufio.NewScanner(file)
Evan Plumlee

14
잊지 마세요 defer file.Close().
Kiril

13
문제는 Scanner.Scan ()이 한 줄에 4096 [] 바이트 버퍼 크기로 제한되어 있다는 것입니다. 당신은 얻을 것이다 bufio.ErrTooLong인 오류를 bufio.Scanner: token too long줄이 너무 긴 경우. 이 경우 bufio.ReaderLine () 또는 ReadString ()을 사용해야합니다.
eduncan911

5
그냥 내 $
0.02-

5
Buffer () 메소드를 사용하여 더 긴 라인을 처리하도록 스캐너를 구성 할 수 있습니다. golang.org/pkg/bufio/#Scanner.Buffer
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]
}

나는 테스트했다 :

  • 버전 이동 1.7 / amd64
  • 버전 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

스캐너 솔루션은 구성하는 경우 긴 행을 처리합니다. 참조 : golang.org/pkg/bufio/#Scanner.Buffer
Inanc Gumus

문서에 표시된 오류를 play.golang.org/p/5CCPzVTSj6 에서 올바르게 확인해야합니다. 즉 err == io.EOF {break} else {return err}
Chuque

53

편집 : go1.1 현재 관용적 솔루션은 bufio 를 사용하는 것입니다.

파일에서 각 줄을 쉽게 읽을 수있는 방법을 작성했습니다. 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에 스캐너 패키지가 있습니다. 내 답변과 동일한 기능을 제공합니다. 스캐너가 stdlib에 있으므로 답변 대신 스캐너를 사용하는 것이 좋습니다. 행복한 해킹! :-)
Malcolm

30

파일을 한 줄씩 읽는 두 가지 일반적인 방법이 있습니다.

  1. bufio.Scanner 사용
  2. bufio.Reader에서 ReadString / ReadBytes / ... 사용

내 테스트 사례 에서 ~ 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

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)만들어 내 자신의 버퍼 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

제 생각에는이 솔루션이 약간 복잡해 보이지 않습니까?
Decebal

10

\ n과 함께 ReadString을 구분 기호로 사용할 수도 있습니다.

  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)있지만 "Hello My Name Is ..."와 같이 공백이있는 모든 문자열을 사용합니다.

var name string = ReadFromStdin()

println(name)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.