如何在Go中一步返回哈希和字节?

时间:2017-01-30 23:35:33

标签: file go hash

我正在尝试理解如何读取文件的内容,计算其哈希并在一个Go中返回其字节。到目前为止,我分两步进行,例如

// calculate file checksum
hasher := sha256.New()
f, err := os.Open(fname)
if err != nil {
    msg := fmt.Sprintf("Unable to open file %s, %v", fname, err)
    panic(msg)
}
defer f.Close()
b, err := io.Copy(hasher, f)
if err != nil {
    panic(err)
}
cksum := hex.EncodeToString(hasher.Sum(nil))

// read again (!!!) to get data as bytes array
data, err := ioutil.ReadFile(fname)

显然,这不是最有效的方法,因为读取发生两次,一次在复制中传递给hasher,另一次在ioutil中读取文件并返回字节列表。我很难理解如何将这些步骤组合在一起并一次完成,读取数据一次,计算任何散列并将其与字节列表一起返回到另一层。

4 个答案:

答案 0 :(得分:3)

如果您想要读取文件而不在内存中创建整个文件的副本,同时计算其哈希值,则可以使用TeeReader来执行此操作:

hasher := sha256.New()
f, err := os.Open(fname)
data := io.TeeReader(f, hasher)
// Now read from data as usual, which is still a stream.

这里发生的是从data读取的任何字节(与Reader一样f就像文件对象hasher一样)将被推送到hasher as好。

但请注意,data只有在通过filter读取整个文件后才生成正确的哈希值,而不是在此之前。因此,如果您在决定是否要读取文件之前需要哈希值,那么您可以选择两次传递(例如,就像您现在一样),或者始终读取文件但丢弃如果哈希检查失败,则会产生结果。

如果你在两遍中读取文件,你当然可以将整个文件数据缓冲在内存中的字节缓冲区中。但是,操作系统通常会缓存您刚才在RAM中读取的文件(如果可能的话),因此自行执行缓冲双通解决方案而不仅仅是对文件执行两次传递的性能优势可能是微不足道的。

答案 1 :(得分:0)

您可以直接将字节写入hasher。例如:

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "io/ioutil"
)

func main() {
    hasher := sha256.New()

    data, err := ioutil.ReadFile("foo.txt")
    if err != nil {
        panic(err)
    }

    hasher.Write(data)
    cksum := hex.EncodeToString(hasher.Sum(nil))

    println(cksum)
}

由于Hash接口嵌入了io.Writer。这允许您从文件中读取一次字节,将它们写入hasher然后也将它们返回。

答案 2 :(得分:-1)

首先data, err := ioutil.ReadFile(fname)。你将获得一些字节。然后创建你的哈希,并做hasher.Write(data)

答案 3 :(得分:-1)

如果您计划散列文件,则不应将整个文件读入内存,因为......有大量文件不适合RAM。是的,在实践中,你很少遇到这种内存不足的问题,但你可以很容易地阻止它们。 Hash界面是io.Writer。通常,Hash包具有返回Hash的New函数。这允许您以块的形式读取文件并持续将其提供给您拥有的哈希的Write方法。您也可以使用io.Copy之类的方法来执行此操作:

h := sha256.New()
data := &bytes.Buffer{}
data.Write([]byte("hi there"))
data.Write([]byte("folks"))
io.Copy(h, data)
fmt.Printf("%x", h.Sum(nil))

io.Copy在内部使用32KiB的缓冲,因此使用它需要大约32KiB的内存。