>백엔드 개발 >Golang >怎么选择 Go 文件读取方案

怎么选择 Go 文件读取方案

Go语言进阶学习
Go语言进阶学习앞으로
2023-07-24 16:02:43879검색
파일 처리는 일반적인 문제입니다. 동시에 Go는 많은 파일 읽기 방법을 제공하므로 사람들이 선택하기가 쉽습니다. 이전에 우리는 10개 이상의 읽기 방법이 나열된 매우 포괄적인 요약: Go에서 파일을 읽는 10가지 방법이라는 기사를 전달했습니다. 확장 기능으로 이 기사에서는 다양한 크기의 실제 파일을 예로 들어 차이점을 자세히 비교합니다.

다양한 크기의 파일 만들기

먼저 비교 객체가 필요합니다. 제한된 컴퓨터 디스크 공간을 고려하여 이 문서에서는 KB, MB, GB의 세 가지 수준에서 파일 읽기의 차이점을 비교합니다.

package main

import (
 "bufio"
 "math/rand"
 "os"
 "time"
)

const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))

func StringWithCharset(length int) string {
 b := make([]byte, length)
 for i := range b {
  b[i] = charset[seededRand.Intn(len(charset))]
 }
 return string(b)
}

func main() {
 files := map[string]int{"4KB.txt": 4, "4MB.txt": 4096, "4GB.txt": 4194304, "16GB.txt": 16777216}
 for name, number := range files {
  file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0666)
  if err != nil {
   panic(err)
  }
  write := bufio.NewWriter(file)
  for i := 0; i < number; i++ {
   s := StringWithCharset(1023) + "\n"
   write.WriteString(s)
  }
  file.Close()
 }
}

위 코드를 실행하면 4KB, 4MB, 4GB, 16GB의 파일이 차례로 생성되며, 이는 한 줄에 1KB의 임의 문자열 내용으로 구성됩니다.

$ ls -alh 4kb.txt 4MB.txt 4GB.txt 16GB.txt
-rw-r--r--  1 slp  staff    16G Mar  6 15:57 16GB.txt
-rw-r--r--  1 slp  staff   4.0G Mar  6 15:54 4GB.txt
-rw-r--r--  1 slp  staff   4.0M Mar  6 15:53 4MB.txt
-rw-r--r--  1 slp  staff   4.0K Mar  6 15:16 4kb.txt

다음으로 다양한 방법을 사용하여 이러한 파일의 내용을 읽습니다.

전체 파일 로드

Go에서는 파일 내용을 한 번에 읽을 수 있는 메소드인 os.ReadFile 및 ioutil.ReadFile을 제공합니다. Go 1.16부터 ioutil.ReadFile은 os.ReadFile과 동일합니다.

func BenchmarkOsReadFile4KB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  _, err := os.ReadFile("./4KB.txt")
  if err != nil {
   b.Fatal(err)
  }
 }
}

func BenchmarkOsReadFile4MB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  _, err := os.ReadFile("./4MB.txt")
  if err != nil {
   b.Fatal(err)
  }
 }
}

func BenchmarkOsReadFile4GB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  _, err := os.ReadFile("./4GB.txt")
  if err != nil {
   b.Fatal(err)
  }
 }
}

func BenchmarkOsReadFile16GB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  _, err := os.ReadFile("./16GB.txt")
  if err != nil {
   b.Fatal(err)
  }
 }
}

파일을 한 번 로드하는 것의 장점과 단점은 매우 명백합니다. IO 수를 줄일 수 있지만, 대용량 파일의 경우 메모리 폭발의 위험이 있습니다.

逐行读取

在很多情况下,例如日志分析,对文件的处理都是按行进行的。Go 中 bufio.Reader 对象提供了一个 ReadLine() 方法,但其实我们更多地是使用 ReadBytes('\n') 或者 ReadString('\n') 代替。

// ReadLine is a low-level line-reading primitive. Most callers should use
// ReadBytes(&#39;\n&#39;) or ReadString(&#39;\n&#39;) instead or use a Scanner.

我们以 ReadString('\n') 为例,对 4 个文件分别进行逐行读取

func ReadLines(filename string) {
 fi, err := os.Open(filename)
 if err != nil{
  panic(err)
 }
 defer fi.Close()
 reader := bufio.NewReader(fi)
 for {
  _, err = reader.ReadString(&#39;\n&#39;)
  if err != nil {
   if err == io.EOF {
    break
   }
   panic(err)
  }
 }
}

func BenchmarkReadLines4KB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  ReadLines("./4KB.txt")
 }
}

func BenchmarkReadLines4MB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  ReadLines("./4MB.txt")
 }
}

func BenchmarkReadLines4GB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  ReadLines("./4GB.txt")
 }
}

func BenchmarkReadLines16GB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  ReadLines("./16GB.txt")
 }
}

块读取

块读取也称为分片读取,这也很好理解,我们可以将内容分成一块块的,每次读取指定大小的块内容。这里,我们将块大小设置为 4KB。

func ReadChunk(filename string) {
 f, err := os.Open(filename)
 if err != nil {
  panic(err)
 }
 defer f.Close()
 buf := make([]byte, 4*1024)
 r := bufio.NewReader(f)
 for {
  _, err = r.Read(buf)
  if err != nil {
   if err == io.EOF {
    break
   }
   panic(err)
  }
 }
}

func BenchmarkReadChunk4KB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  ReadChunk("./4KB.txt")
 }
}

func BenchmarkReadChunk4MB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  ReadChunk("./4MB.txt")
 }
}

func BenchmarkReadChunk4GB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  ReadChunk("./4GB.txt")
 }
}

func BenchmarkReadChunk16GB(b *testing.B) {
 for i := 0; i < b.N; i++ {
  ReadChunk("./16GB.txt")
 }
}

汇总结果

BenchmarkOsReadFile4KB-8           92877             12491 ns/op
BenchmarkOsReadFile4MB-8            1620            744460 ns/op
BenchmarkOsReadFile4GB-8               1        7518057733 ns/op
signal: killed

BenchmarkReadLines4KB-8            90846             13184 ns/op
BenchmarkReadLines4MB-8              493           2338170 ns/op
BenchmarkReadLines4GB-8                1        3072629047 ns/op
BenchmarkReadLines16GB-8               1        12472749187 ns/op

BenchmarkReadChunk4KB-8            99848             12262 ns/op
BenchmarkReadChunk4MB-8              913           1233216 ns/op
BenchmarkReadChunk4GB-8                1        2095515009 ns/op
BenchmarkReadChunk16GB-8               1        8547054349 ns/op

在本文的测试条件下(每行数据 1KB),对于小对象 4KB 的读取,三种方式差距并不大;在 MB 级别的读取中,直接加载最快,但块读取也慢不了多少;上了 GB 后,块读取方式会最快。

且有一点可以注意到的是,在整个文件加载的方式中,对于 16 GB 的文件数据(测试机器运行内存为 8GB),会内存耗尽出错,没法执行。

总结

不管是什么大小的文件,均不推荐整个文件加载的方式,因为它在小文件时的速度优势并没有那么大,相较于安全隐患,不值得选择它。

块读取是优先选择,尤其对于一些没有换行符的文件,例如音视频等。通过设定合适的块读取大小,能让速度和内存得到很好的平衡。且在读取过程中,往往伴随着处理内容的逻辑。每块内容可以赋给一个工作 goroutine 来处理,能更好地并发。

------------------- End -------------------

과거의 멋진 기사 추천: ​​

  • Go 언어에서 반사의 기본을 가르쳐주는 기사

  • Go 언어 기본 구조(겨울)

  • Go 언어 맵의 기본을 이해하는 데 도움이 될 기사입니다

怎么选择 Go 文件读取方案

모두 환영합니다좋아요,댓글,앞으로, repost, 여러분의 회사와 지원에 감사드립니다

Go 스터디그룹에 참여하고 싶다면 백그라운드에서 답글을 남겨주세요. [Join the group]

수천 개의 강과 산은 언제나 사랑을 가져다줍니다. [Looking]

을 클릭해 주세요.

위 내용은 怎么选择 Go 文件读取方案의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 Go语言进阶学习에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제
이전 기사:다음 기사:

관련 기사

더보기