创建不同大小的文件
首先,我们需要有比较对象。鉴于电脑磁盘空间有限,本文就比较 KB、MB、GB 三个级别的文件读取差异。
package mainimport ( "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执行以上代码,我们依次得到 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一次性加载文件的优缺点非常明显,它能减少 IO 次数,但它会将文件内容都加载至内存中,对于大文件,存在内存撑爆的风险。
逐行读取
在很多情况下,例如日志分析,对文件的处理都是按行进行的。Go 中 bufio.Reader 对象提供了一个 ReadLine() 方法,但其实我们更多地是使用 ReadBytes('') 或者 ReadString('') 代替。
// ReadLine is a low-level line-reading primitive. Most callers should use// ReadBytes('') or ReadString('') instead or use a Scanner.登录后复制
我们以 ReadString('') 为例,对 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('') if err != nil { if err == io.EOF { break } panic(err) } }}func BenchmarkReadLines4KB(b *testing.B) { for i := 0; i块读取
块读取也称为分片读取,这也很好理解,我们可以将内容分成一块块的,每次读取指定大小的块内容。这里,我们将块大小设置为 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汇总结果
BenchmarkOsReadFile4KB-8 92877 12491 ns/opBenchmarkOsReadFile4MB-8 1620 744460 ns/opBenchmarkOsReadFile4GB-8 1 7518057733 ns/opsignal: killedBenchmarkReadLines4KB-8 90846 13184 ns/opBenchmarkReadLines4MB-8 493 2338170 ns/opBenchmarkReadLines4GB-8 1 3072629047 ns/opBenchmarkReadLines16GB-8 1 12472749187 ns/opBenchmarkReadChunk4KB-8 99848 12262 ns/opBenchmarkReadChunk4MB-8 913 1233216 ns/opBenchmarkReadChunk4GB-8 1 2095515009 ns/opBenchmarkReadChunk16GB-8 1 8547054349 ns/op登录后复制
在本文的测试条件下(每行数据 1KB),对于小对象 4KB 的读取,三种方式差距并不大;在 MB 级别的读取中,直接加载最快,但块读取也慢不了多少;上了 GB 后,块读取方式会最快。
且有一点可以注意到的是,在整个文件加载的方式中,对于 16 GB 的文件数据(测试机器运行内存为 8GB),会内存耗尽出错,没法执行。
总结
不管是什么大小的文件,均不推荐整个文件加载的方式,因为它在小文件时的速度优势并没有那么大,相较于安全隐患,不值得选择它。
块读取是优先选择,尤其对于一些没有换行符的文件,例如音视频等。通过设定合适的块读取大小,能让速度和内存得到很好的平衡。且在读取过程中,往往伴随着处理内容的逻辑。每块内容可以赋给一个工作 goroutine 来处理,能更好地并发。
——————— End ———————–
往期精彩文章推荐:
一篇文章教会你Go语言基础之反射
Go语言基础之结构体(冬日篇)
一篇文章带你了解Go语言基础之map

欢迎大家点赞,留言,转发,转载,感谢大家的相伴与支持
想加入Go学习群请在后台回复【入群】
万水千山总是情,点个【在看】行不行
以上就是怎么选择 Go 文件读取方案的详细内容,更多请关注【创想鸟】其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。
发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2487343.html