Maison >développement back-end >Golang >Comment choisir une solution de lecture de fichiers Go
Tout d'abord, nous devons avoir un objet de comparaison. Compte tenu de l'espace disque limité de l'ordinateur, cet article compare les différences de lecture de fichiers aux trois niveaux de Ko, Mo et Go.
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() } }
En exécutant le code ci-dessus, nous obtenons des fichiers de 4 Ko, 4 Mo, 4 Go et 16 Go en séquence, qui sont composés du contenu de chaînes aléatoires de 1 Ko par ligne.
$ 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
Ensuite, nous utilisons différentes manières pour lire le contenu de ces fichiers.
Go fournit des méthodes pour lire le contenu du fichier en même temps : os.ReadFile et ioutil.ReadFile. À partir de Go 1.16, ioutil.ReadFile est équivalent à 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) } } }
Les avantages et les inconvénients du chargement unique des fichiers sont très évidents. Cela peut réduire le nombre d'IO, mais cela chargera tout le contenu du fichier dans la mémoire. Pour les fichiers volumineux, il existe un risque d'explosion de la mémoire.
在很多情况下,例如日志分析,对文件的处理都是按行进行的。Go 中 bufio.Reader 对象提供了一个 ReadLine() 方法,但其实我们更多地是使用 ReadBytes('\n') 或者 ReadString('\n') 代替。
// ReadLine is a low-level line-reading primitive. Most callers should use // ReadBytes('\n') or ReadString('\n') 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('\n') 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 -------------------
Merveilleux articles recommandés du passé :
Un article vous apprend les bases de la réflexion en langage Go
Structure des bases du langage Go (hiver)
Un article vous amènera à comprendre les bases de la carte du langage Go
Bienvenue à tousaimez, commentez, en avant, réimpression, Merci à tous pour votre compagnie et votre soutien
Si vous souhaitez rejoindre le groupe d'étude Go, veuillez répondre en arrière-plan [Rejoindre le groupe]
Des milliers de montagnes et de rivières ont toujours de l'amour, pouvez-vous cliquer sur [Looking]
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!