[string] Go에서 한 줄씩 파일 읽기

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



답변

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

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

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

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


답변

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().


답변

사용하다:

  • 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.


답변

편집 : 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)
}

건배!


답변

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

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


답변

요지의

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
}


답변

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