python中用于命名缓存文件的最短哈希

时间:2009-08-19 22:43:52

标签: python hash

python中可用的最短哈希(文件名可用形式,如hexdigest)是什么?我的应用程序想要保存某些对象的缓存文件。对象必须具有唯一的repr(),因此它们用于“种子”文件名。我想为每个对象生成一个可能唯一的文件名(不是那么多)。它们不应该发生冲突,但是如果它们发生了我的应用程序将只缺少该对象的缓存(并且必须重新索引该对象的数据,这是应用程序的一个小成本)。

因此,如果发生一次冲突,我们会丢失一个缓存文件,但是收集缓存所有对象的节省使得应用程序启动速度更快,因此无关紧要。

现在我实际上正在使用abs(hash(repr(obj)));那是对的,字符串哈希!还没有找到任何碰撞,但我希望有更好的哈希函数。 hashlib.md5在python库中可用,但如果放入文件名,则hexdigest非常长。替代方案,具有合理的碰撞阻力?

编辑: 用例是这样的: 数据加载器获取数据携带对象的新实例。独特的类型有独特的repr。因此,如果存在hash(repr(obj))的缓存文件,我将取消删除该缓存文件并将obj替换为unpickled对象。如果发生碰撞并且缓存是假匹配,我注意到。因此,如果我们没有缓存或具有错误匹配,我改为初始化obj(重新加载其数据)。

结论(?)

python中的str哈希可能已经足够好了,我只担心它的碰撞阻力。但是如果我可以使用它来散列2**16个对象,那么它就足够了。

我发现如何使用十六进制哈希(来自任何哈希源)并使用base64紧凑地存储它:

# 'h' is a string of hex digits 
bytes = "".join(chr(int(h[i:i+2], 16)) for i in xrange(0, len(h), 2))
hashstr = base64.urlsafe_b64encode(bytes).rstrip("=")

8 个答案:

答案 0 :(得分:37)

birthday paradox适用:给定一个好的散列函数,碰撞前预期的散列数约为sqrt(N),其中N是散列函数可以采用的不同值的数量。 (我指出的维基百科条目给出了确切的公式)。因此,例如,如果你想使用不超过32位,你的碰撞担心对于大约64K对象(即2**16对象 - 你的哈希值2**32不同值的平方根是严重的功能可以)。你期望有多少个物体,如一个数量级?

既然你提到碰撞是一个小麻烦,我建议你的目标是一个散列长度大致是你所拥有的物体数量的平方,或者稍微少一点但不比那个少。

你想创建一个文件名 - 就像区分大小写的文件系统一样,在Unix上是典型的,或者你是否也必须满足不区分大小写的系统?这很重要,因为你的目标是短文件名,但是你可以用来表示你的哈希作为文件名的每个字符的位数在区分大小和不敏感的系统上发生了显着变化。

在区分大小写的系统上,您可以使用标准库的base64模块(我推荐编码的“urlsafe”版本,即this函数,作为避免'/'字符存在于普通base64中,在Unix文件名中很重要)。这为每个字符提供了6个可用位,比十六进制中的4位/字符要好得多。

即使在不区分大小写的系统上,你仍然可以比hex更好 - 使用base64.b32encode并获得每个字符5位。

这些函数接收并返回字符串;如果您选择的哈希函数生成数字,请使用struct模块将数字转换为字符串。

如果你确实有几万个对象,我认为你可以使用内置哈希(32位,所以6-7个字符,具体取决于你选择的编码)。对于一百万个对象,你需要40位左右(7或8个字符) - 你可以折叠(xor,不要截断;-) sha256到一个长的,有一个合理的位数,比如128左右,并在编码之前使用%运算符将其进一步缩小到所需的长度。

答案 1 :(得分:27)

字符串的内置哈希函数是相当无冲突的,也很短。它具有2**32个值,因此您不太可能遇到冲突(如果使用其abs值,它将只有2**31个值。

您一直在要求最短的哈希函数。那当然是

def hash(s):
  return 0

但我想你并不是那么真意......

答案 2 :(得分:7)

您可以通过简单地截断它来缩短您喜欢的任何哈希值。 md5总是32个十六进制数字,但它的任意子串(或任何其他散列)具有散列的适当特性:相等的值产生相等的散列,并且值围绕一堆散布。

答案 3 :(得分:3)

我确信在Python中有一个CRC32实现,但这可能太短(8个十六进制数字)。在好的方面,它非常快。

找到它,binascii.crc32

答案 4 :(得分:1)

如果你确实发生了碰撞,你怎么会说它确实发生了?

如果我是你,我会使用hashlib sha1() repr(),然后获取它的有限子字符串(例如前16个字符)。

除非你在谈论大量的这些对象,否则我建议你只使用完整的哈希。那么碰撞的机会是如此,那么,如此之小,以至于你永远不会活着看到它(很可能)。

另外,如果您正在处理那个多个文件,我猜测应该调整您的缓存技术以适应它。

答案 5 :(得分:1)

我们使用hashlib.sha1.hexdigest()为缓存对象生成更长的字符串,并取得了很好的成功。无论如何,没有人真正在查看缓存文件。

答案 6 :(得分:1)

考虑到您的用例,如果您没有使用单独的缓存文件,并且距离该开发路径不太远,则可以考虑使用shelve模块。

这将为您提供存储对象的持久字典(存储在单个dbm文件中)。蚀刻/去除涂层是透明地执行的,您不必担心散列,碰撞,文件I / O等。

对于搁置字典键,您只需使用repr(obj)并让shelve处理为您存储对象。一个简单的例子:

import shelve
cache = shelve.open('cache')
t = (1,2,3)
i = 10
cache[repr(t)] = t
cache[repr(i)] = i
print cache
# {'(1, 2, 3)': (1, 2, 3), '10': 10}
cache.close()

cache = shelve.open('cache')
print cache
#>>> {'(1, 2, 3)': (1, 2, 3), '10': 10}
print cache[repr(10)]
#>>> 10

答案 7 :(得分:0)

短哈希表示您可能对两个不同的文件具有相同的哈希值。大哈希也可能发生同样的事情,但它的方式更为罕见。 也许这些文件名应根据其他参考文献而变化,例如microtime(除非这些文件可能创建得太快)。