在golang中散列多个值

时间:2017-11-07 11:31:40

标签: go hash hash-collision gob

我目前正在开发一个需要缓存不同资源的应用程序。如果我们必须重建资源或者我们是否可以从缓存中获取资源,则不同类型的资源具有将知道哪些数据相关的处理程序。为此,处理程序应生成用于缓存的所有相关数据的哈希。根据上下文,数据可以是基元(int,float,...),字符串,切片,结构和映射。几乎一切都如此。用于散列的对象数也可能不同。

为了计算处理程序中的哈希值,我创建了一个散列函数,其变量参数类型为interface{}

我目前的做法是:

func Hash(objs ...interface{})([]byte) {
    // Use MD5 because it's fast and is reasonably enough protected against accidental collisions.
    // There is no scenario here where intentional created collisions could do harm.
    digester := crypto.MD5.New()

    encoder := gob.NewEncoder(digester)
    encoder.Encode(objs) // In real life one would handle that error

    return digester.Sum(make([]byte, 0))
}

这很有效。但有一些事情让我对这种实现感到头疼。因为我不确定gob will always behave deterministic,对于当前版本似乎是这种情况,但正如引用的答案所指出的那样,版本之间可能会有变化。 根据gob的文档,在传输结构时将省略默认值(0表示整数,空字符串,零,......)。此外,所有int值都将作为通用数字传输。所以unit64和int将是相同的。对于我的用例,我无法想到这个问题的实际问题,但这有点麻烦。

现在,如果我从头开始编写该函数,我会正确地播放它,使用反射遍历结构并创建一个散列树。但我不想这样做。

我非常确定我不是第一个有这些要求的人,但我无法在网上找到任何经过良好测试的go代码,解决了这个问题。< / p>

附录

另见:https://crypto.stackexchange.com/questions/10058/how-to-hash-a-list-of-multiple-items

这并不像看起来那么微不足道。简单地按照Adrian的指示连接数据不会起作用,因为Hash("12", "3")Hash("123")会产生相同的哈希值。一种可能的解决方案是在散列之前预先设置数据的长度(以及列表元素的数量),或者创建一个哈希树,但我无法想到用复杂数据结构做任何一种方法的可靠方法,没有编写大量的反射代码来处理所有不同的情况(整数,浮点数,字符串,结构,切片......)并遍历整个结构。我想避免这种情况,因为有很多特殊情况可以监督这样做,而且对我来说似乎没那么复杂。这就是我尝试使用序列化解决问题的原因,这将解决上述大多数问题。我不确定在这种情况下是否存在使用gob的缺点,以及是否有更智能的解决方案。

2 个答案:

答案 0 :(得分:3)

或许可以添加到Adrian的答案。如果在每个对象之前添加fmt.Fprintf(digester, reflect.TypeOf(ob))。这将使哈希(“12”,“3”)和哈希(“123”)不同。

package main

import (
    "crypto"
    _ "crypto/md5"
    "fmt"
    "reflect"
)

func main() {
    fmt.Printf("%x\n", Hash("12", "3"))
    fmt.Printf("%x\n", Hash("123"))
}

func Hash(objs ...interface{}) []byte {
    digester := crypto.MD5.New()
    for _, ob := range objs {
        fmt.Fprint(digester, reflect.TypeOf(ob))
        fmt.Fprint(digester, ob)
    }
    return digester.Sum(nil)
}

输出:

c3d5dcf1d7540d3e46e7d7b5a8c6e8ae
787ca7e12a2fa991cea5051a64b49d0c

https://play.golang.org/p/nufD3wTJkb

答案 1 :(得分:1)

Hash providers implement io.Writer,这意味着您可以根据需要向他们写入数据 - 您不必通过一次调用Sum来完成所有操作:

for _,ob := range objs {
    fmt.Fprint(digester, ob)
}
return digester.Sum(nil)

工作场所示例:https://play.golang.org/p/HtObhrmoaP

如果您需要支持非基本类型,可以使用以下内容:

fmt.Fprintf(digester, "%+v", ob)

甚至只是:

json.NewEncoder(digester).Encode(&objs)

gob一样,json 的输出可能在Go版本之间发生变化,但由于JSON是一种非常稳定的格式且具有非常稳定的实现,因此似乎不太可能。