[string] Go에서 한 줄씩 파일 읽기
file.ReadLine
Go에서 기능 을 찾을 수 없습니다 . 빠르게 작성하는 방법을 알 수는 있지만 여기서 뭔가를 간과하고 있는지 궁금합니다. 파일을 한 줄씩 읽는 방법은 무엇입니까?
답변
참고 : 허용되는 답변은 초기 버전의 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
반환 된 문자열의 끝에를 유지합니다 .
- 라인이 매우 길 수 있다고 생각하지 않으면 (즉, 많은 RAM을 사용)
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)
}
건배!
답변
파일을 한 줄씩 읽는 두 가지 일반적인 방법이 있습니다.
- bufio.Scanner 사용
- 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
}
}