ホームページ  >  記事  >  バックエンド開発  >  Go ファイル読み取りソリューションの選択方法

Go ファイル読み取りソリューションの選択方法

Go语言进阶学习
Go语言进阶学习転載
2023-07-24 16:02:43803ブラウズ
ファイル処理はよくある問題ですが、同時に、Go にはファイルの読み取りメソッドが多数提供されているため、選択が難しくなりがちです。以前、超包括的な概要: Go でファイルを読み取る 10 の方法 という記事を転送し、10 を超える読み取り方法をリストしました。この記事では、拡張機能として、さまざまなサイズの実際のファイルを例として取り上げ、その違いを詳細に比較します。

異なるサイズのファイルを作成する

まず、比較オブジェクトが必要です。コンピューターのディスク容量には限りがあるため、この記事では、KB、MB、GB の 3 つのレベルでのファイル読み取りの違いを比較します。

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

上記のコードを実行すると、1 行あたり 1KB のランダムな文字列の内容で構成される 4KB、4MB、4GB、16GB のファイルが順番に取得されます。

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

ファイルを 1 回だけ読み込むことの長所と短所は非常に明白です。これにより 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 ファイル読み取りソリューションの選択方法
##皆さんようこそ

いいね、メッセージを残す、転送、再版、皆さんありがとうコンパニオンとサポート

Go 研究グループに参加したい場合は、バックグラウンドで返信してください [ グループに参加してください ]

愛は何千もの川や山を越えても常に同じです、[在看]

# をクリックしていただけますか
##

以上がGo ファイル読み取りソリューションの選択方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はGo语言进阶学习で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。