最近我正在为 go 中的文件创建校验和。我的代码正在处理小文件和大文件。我尝试了两种方法,第一种使用 ioutil.ReadFile("filename")
第二个正在与 os.Open("filename")
合作.
示例:
第一个函数与 io/ioutil
一起使用并且适用于小文件。当我尝试复制一个大文件时,我的内存会受到爆炸,对于 1.5GB 的 iso,它会使用 3GB 的内存。
func byteCopy(fileToCopy string) {
file, err := ioutil.ReadFile(fileToCopy) //1.5GB file
omg(err) //error handling function
ioutil.WriteFile("2.iso", file, 0777)
os.Remove("2.iso")
}
当我想使用 crypto/sha512
创建校验和时,情况更糟和io/ioutil
。
它永远不会完成并中止,因为它耗尽了内存。
func ioutilHash() {
file, _ := ioutil.ReadFile(iso)
h := sha512.New()
fmt.Printf("%x", h.Sum(file))
}
当使用下面的函数时,一切正常。
func ioHash() {
f, err := os.Open(iso) //iso is a big ~ 1.5tb file
omg(err) //error handling function
defer f.Close()
h := sha512.New()
io.Copy(h, f)
fmt.Printf("%x", h.Sum(nil))
}
我的问题:
为什么是ioutil.ReadFile()
功能无法正常工作? 1.5GB 的文件不应该填满我的 16GB 内存。我现在不知道该去哪里看。
有人可以解释一下这些方法之间的差异吗?阅读 go-doc 和示例后我不明白。
拥有可用的代码固然很好,但理解其工作原理远不止于此。
提前致谢!
最佳答案
以下代码不会执行您认为的操作。
func ioutilHash() {
file, _ := ioutil.ReadFile(iso)
h := sha512.New()
fmt.Printf("%x", h.Sum(file))
}
这首先读取您的 1.5GB iso。正如 jnml 所指出的,它不断地创建越来越大的缓冲区来填充它。最终,总缓冲区大小不小于1.5GB且不大于1.875GB(按当前实现)。
但是,之后您又创建了另一个缓冲区! h.Sum(file)
不会对文件进行哈希处理。它将当前的哈希值附加到文件中!这可能会也可能不会导致另一次分配。
真正的问题是您正在获取该文件,现在附加了哈希值,并使用 %x 打印它。 fmt实际上使用jnml指出的ioutil.ReadAll使用的相同类型的方法进行预计算。因此它不断分配越来越大的缓冲区来存储文件的十六进制。由于每个字母都是 4 位,这意味着我们正在讨论不少于 3GB 且不大于 3.75GB 的缓冲区。
这意味着您的事件缓冲区可能有 5.625GB。再加上 GC 并不完美并且没有删除所有中间缓冲区,它很容易就会填满您的空间。
编写该代码的正确方法是。
func ioutilHash() {
file, _ := ioutil.ReadFile(iso)
h := sha512.New()
h.Write(file)
fmt.Printf("%x", h.Sum(nil))
}
这几乎没有完成分配的数量。
底线是 ReadFile 很少是您想要使用的。当可以选择时,IO 流(使用读取器和写入器)始终是最好的方法。使用 io.Copy 时,您不仅可以减少分配,还可以同时散列和读取磁盘。在您的 ReadFile 示例中,当两个资源不相互依赖时,它们会同步使用。
关于file-io - go 中的文件读取和校验和。方法之间的差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18430936/