文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

怎么选择 Go 文件读取方案

2024-12-02 06:09

关注

创建不同大小的文件

首先,我们需要有比较对象。鉴于电脑磁盘空间有限,本文就比较 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('\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 来处理,能更好地并发。

来源:Golang技术分享内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯