在 golang 中散列多个值
Hashing multiple values in golang
我目前正在开发一个需要缓存不同资源的应用程序。不同类型的资源都有处理程序,它们将知道哪些数据与确定相关,我们是否必须重建资源或是否可以从缓存中获取它。为此,处理程序应生成所有相关数据的哈希值以进行缓存。根据上下文,数据可以是基元(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 的文档,在传输结构时将省略默认值(整数为 0,空字符串,nil,...)。此外,所有 int 值都将作为通用数字传输。所以 unit64 和 int 是一样的。对于我的用例,我想不出这有什么实际问题,但这闻起来像是麻烦的根源。
现在,如果我要从头开始编写该函数,我会妥善保管它,使用反射遍历结构并创建哈希树。但我不想那样做。
我很确定我不是第一个提出这些要求的人,但我无法在网上找到任何经过良好测试的 go 代码来解决这个问题。
附录
另见:https://crypto.stackexchange.com/questions/10058/how-to-hash-a-list-of-multiple-items
这并不像看起来那么微不足道。正如 Adrian 指出的那样简单地连接数据是行不通的,因为 Hash("12", "3")
和 Hash("123")
将生成相同的散列。一种可能的解决方案是在散列之前添加数据长度(以及列表元素的数量)或创建散列树,但我想不出一种可靠的方法来处理复杂的数据结构,而无需编写大量反射代码来处理所有不同的情况(整数、浮点数、字符串、结构、切片……)并遍历整个结构。我想避免这种情况,因为这样做可以监督很多特殊情况,这对我来说似乎没有必要复杂化。这就是为什么我尝试使用序列化来解决问题,这将解决上述大部分问题。我只是不确定在这种情况下使用 gob 是否有一些缺点,是否没有更聪明的解决方案。
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 是一个非常稳定的格式和非常稳定的实现。
也许可以添加到 Adrian 的回答中。如果在每个对象前加上fmt.Fprintf(digester, reflect.TypeOf(ob))
。这将使 Hash("12", "3") 和 Hash("123") 不同。
主要包
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
我目前正在开发一个需要缓存不同资源的应用程序。不同类型的资源都有处理程序,它们将知道哪些数据与确定相关,我们是否必须重建资源或是否可以从缓存中获取它。为此,处理程序应生成所有相关数据的哈希值以进行缓存。根据上下文,数据可以是基元(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))
}
这行得通。但是有几件事让我对这个实现感到头疼。因为我不确定
现在,如果我要从头开始编写该函数,我会妥善保管它,使用反射遍历结构并创建哈希树。但我不想那样做。
我很确定我不是第一个提出这些要求的人,但我无法在网上找到任何经过良好测试的 go 代码来解决这个问题。
附录
另见:https://crypto.stackexchange.com/questions/10058/how-to-hash-a-list-of-multiple-items
这并不像看起来那么微不足道。正如 Adrian 指出的那样简单地连接数据是行不通的,因为 Hash("12", "3")
和 Hash("123")
将生成相同的散列。一种可能的解决方案是在散列之前添加数据长度(以及列表元素的数量)或创建散列树,但我想不出一种可靠的方法来处理复杂的数据结构,而无需编写大量反射代码来处理所有不同的情况(整数、浮点数、字符串、结构、切片……)并遍历整个结构。我想避免这种情况,因为这样做可以监督很多特殊情况,这对我来说似乎没有必要复杂化。这就是为什么我尝试使用序列化来解决问题,这将解决上述大部分问题。我只是不确定在这种情况下使用 gob 是否有一些缺点,是否没有更聪明的解决方案。
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 是一个非常稳定的格式和非常稳定的实现。
也许可以添加到 Adrian 的回答中。如果在每个对象前加上fmt.Fprintf(digester, reflect.TypeOf(ob))
。这将使 Hash("12", "3") 和 Hash("123") 不同。
主要包
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