Golang读取文件的常见方案和效率对比,在谷歌搜索有一大堆,有的不够严谨,或者缺少实用性,有如下几个常见问题:
so,我决定深入研究后,重新写一篇评测记录:
+++++++++++++++分割线+++++++++++++++
读取文件主要是三种方法(我下面罗列了四组方案,最后2组是来自同一个方法)
直接说结论吧:
package file
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
)
var (
fi *os.File
err error
)
func fileClose() {
err = fi.Close()
}
func ReadCom(path string) string {
fi, err = os.Open(path)
if err != nil {
fmt.Printf("open err : %v\n", err)
}
defer fileClose()
var chunks []byte
for {
block := make([]byte, 1024) //这句要放在for里面(或者将后面block换做block[:readNum]),网络上搜索的不少这里没注意,会造成文件读出多余的字符
readNum, err := fi.Read(block)
if err != nil && err != io.EOF {
panic(err)
}
chunks = append(chunks, block...)
if 0 == readNum {
break
}
}
return string(chunks)
}
func ReadBufio(path string) string {
fi, err = os.Open(path)
if err != nil {
fmt.Printf("open err : %v\n", err)
}
defer fileClose()
// 将文件字节流全部放入对io.Reader做了二次封装的bufio.Reader对象,设置缓冲空间大小为默认4KB
bufReader := bufio.NewReader(fi)
var chunks []byte //做一个可变容器
//var chunks = bytes.NewBuffer(make([]byte, 1024)) //您也可以用这个做容器
block := make([]byte, 1024) //make一个自定义大小的切片,相当于二级缓冲
for {
//用io.Reader将4KB缓冲里面的字节流再次分块读取到p,这样的流程减少了io访问次数
readNum, err := bufReader.Read(block)
if err != nil && err != io.EOF {
panic(err)
}
if 0 == readNum {
break
}
chunks = append(chunks, block[:readNum]...)
//chunks.Write(block) //您也可以用这个做拼接,然后return chunks.String()
}
return string(chunks)
}
func ReadIoutil(path string) string {
fi, err = os.Open(path)
if err != nil {
fmt.Printf("open err : %v\n", err)
}
defer fileClose()
//直接调用了ioutil.readAll(),未计算文件大小,直接设置缓冲容器大小为512字节,最终调用底层的io.Reader
byt, err := ioutil.ReadAll(fi)
if err != nil {
fmt.Printf("ioutil read all err : %v\n", err)
}
return string(byt)
}
func ReadIoutilLite(path string) string {
//先计算文件大小,再调用ioutil.readAll(),并设置缓冲容器大小 = 文件大小+512字节,速度最快,但是也最耗内存
byt, err := ioutil.ReadFile(path)
if err != nil {
fmt.Printf("open err : %v\n", err)
}
return string(byt)
}
计算每个函数循环10次的总时间。
func main() {
var s string
path := "src/file"
t1 := time.Now()
for i := 0; i < 10; i++ {
_ = file.ReadCom(path + "1")
}
fmt.Printf("[ReadCom] cost time %v \n", time.Since(t1))
t2 := time.Now()
for i := 0; i < 10; i++ {
_ = file.ReadBufio(path + "2")
}
fmt.Printf("[ReadBufio] cost time %v \n", time.Since(t2))
t3 := time.Now()
for i := 0; i < 10; i++ {
_ = file.ReadIoutil(path + "4")
}
fmt.Printf("[ReadIoutil] cost time %v \n", time.Since(t3))
t4 := time.Now()
for i := 0; i < 10; i++ {
_ = file.ReadIoutilLite(path + "5")
}
fmt.Printf("[ReadIoutilLite] cost time %v \n", time.Since(t4))
fmt.Printf("%s \n", s)
}
如果是低于1KB的配置文件,那么各个读取函数的差别并不大,甚至会有偶尔浮动,只是ReadCom在初次读取的时候速度明显会慢点。
如下:
不相上下
[ReadCom] cost time 288.859µs
[ReadBufio] cost time 291.319µs
[ReadIoutil] cost time 273.937µs
[ReadIoutilLite] cost time 324.104µs
再来一次,下面情况还是多点
[ReadCom] cost time 316.775µs
[ReadBufio] cost time 316.745µs
[ReadIoutil] cost time 274.387µs
[ReadIoutilLite] cost time 276.21µs
14KB的配置文件,开始能看到差距了,ReadCom速度总是最慢的
如下:
[ReadCom] cost time 907.104µs
[ReadBufio] cost time 883.849µs
[ReadIoutil] cost time 613.267µs
[ReadIoutilLite] cost time 418.175µs
如果是100MB的大文件,差距已经很明显了,而且值得注意的是用block := make([]byte, 1024)设置block切片大小的时候,越靠近文件大小,Bufio和ReadCom的速度越快,甚至接近Ioutil方案。
如下:
[ReadCom] cost time 2.636320426s
[ReadBufio] cost time 1.550074715s
[ReadIoutil] cost time 510.43783ms
[ReadIoutilLite] cost time 313.291665ms
但是做下修改:block := make([]byte, 1024*1024*100)之后呢?
[ReadCom] cost time 604.839308ms
[ReadBufio] cost time 463.107829ms
[ReadIoutil] cost time 593.417837ms
[ReadIoutilLite] cost time 303.74246ms
1GB以上,怕机器撑不住就没测了。
其实无论那种方案,读取的原理都差不多,基本是靠缓冲来加速,但有一个影响效率的不可忽视的关键:由于ReadBufio和ReadCom需要自己写for循环读缓冲,然后用append或者bytes.Buffer对Byte字符进行拼接,而这个拼接过程其实会明显拖慢时间(除非你只是测试,不真正的读取文件内容),所以你需要根据内存占用,文件大小和读取频率需求做针对性优化,否则效率要低很多。
对于常见的1MB以内的配置文件,等你优化完之后,会发现还不如直接使用ioutil.ReadFile来得省心和高效。
以上,个人原创,可能还有更优方案。