【问题标题】:Shortest hash in python to name cache filespython中最短的哈希来命名缓存文件
【发布时间】:2010-11-21 03:23:35
【问题描述】:

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

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

现在我实际上正在使用 abs(hash(repr(obj)));没错,就是字符串哈希!还没有发现任何冲突,但我想要一个更好的哈希函数。 hashlib.md5 在 python 库中可用,但如果放入文件名,则 hexdigest 真的很长。具有合理抗碰撞性的替代品?

编辑: 用例是这样的: 数据加载器获取数据承载对象的新实例。独特的类型有独特的代表。因此,如果存在 hash(repr(obj)) 的缓存文件,我将取消该缓存文件并将 obj 替换为未腌制的对象。如果发生冲突并且缓存是错误匹配,我会注意到。因此,如果我们没有缓存或存在错误匹配,我会改为初始化 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("=")

【问题讨论】:

  • 为什么要关心文件名的长度?这根本不重要,除非您使用的是愚蠢的文件系统
  • 很难看。所有的程序员都想用更多的东西表达更少的东西,而我知道我可以,一个完整的加密哈希是多余的。
  • 在最后的示例中,对于 python hashlib 哈希,您当然可以使用 bytes = (..).digest()。
  • 您不应该使用内置哈希,因为它不能保证在会话(或体系结构,尽管如果所有缓存文件都存储在本地,这可能与您的情况无关)是持久的。事实上,从 Python 3.3 开始,它保证对字符串是随机的。您应该考虑使用手写函数,例如this

标签: python hash


【解决方案1】:

birthday paradox 适用:给定一个好的散列函数,发生冲突之前的预期散列数约为 sqrt(N),其中 N 是散列函数可以采用的不同值的数量。 (我指出的维基百科条目给出了确切的公式)。因此,例如,如果您想使用不超过 32 位,那么您对大约 64K 对象的冲突担忧是严重的(即,2**16 对象 - 您的哈希函数可以采用的 2**32 不同值的平方根) .您希望有多少个对象,按数量级计算?

由于您提到碰撞是一个小麻烦,我建议您将哈希长度的目标设置为大致等于您将拥有的对象数量的平方,或者稍微少一点但不会比这少很多。

你想创建一个文件名——是在区分大小写的文件系统上,就像在 Unix 上典型的那样,还是你也必须迎合不区分大小写的系统?这很重要,因为您的目标是短文件名,但是在区分大小写和不区分大小写的系统上,您可以用来将哈希表示为文件名的每个字符的位数会发生巨大变化。

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

即使在不区分大小写的系统上,您仍然可以做得比十六进制更好——使用 base64.b32encode 并获得每个字符 5 位。

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

如果您确实有数万个对象,我认为您可以使用内置哈希(32 位,因此 6-7 个字符,具体取决于您选择的编码)。对于一百万个对象,您需要 40 位左右(7 或 8 个字符)——您可以将 sha256 折叠(异或,不要截断;-)将 sha256 折叠为具有合理位数的 long,例如 128 位左右, 并在编码前使用% 运算符将其进一步切割成您想要的长度。

【讨论】:

  • 在 python3 中,base64.b32encode 处理的是字节而不是字符串
  • 首先使用int.to_bytes()hashlib 模块将str 编码为bytes 或生成基于bytes 的哈希表示应该不难。
【解决方案2】:

字符串的内置散列函数相当无冲突,而且相当短。它具有2**32 值,因此您不太可能遇到冲突(如果使用它的abs 值,它将只有2**31 值)。

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

def hash(s):
  return 0

但我猜你不是那个意思...

【讨论】:

  • 好吧,我想避免碰撞 :-)
【解决方案3】:

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

【讨论】:

  • 截断的越多,两个不同文件的相同哈希值的几率就越高。问题是“什么赔率是可以接受的?”截断时,您会遭受“误报”:哈希匹配,但对象不同。
  • 是的,完全正确。对于任何哈希,您需要确定可接受的碰撞风险,并评估您的风险。
【解决方案4】:

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

找到了,binascii.crc32

【讨论】:

  • 确实很快,这很好。但是看到不推荐作为hash函数,或许string的hash()也一样好?
  • CRC 不推荐作为哈希,因为它会产生冲突,而且相对容易故意。这使得它不安全,例如散列密码。但它一个散列函数,它只是生成一个很短的散列。这意味着更多潜在的碰撞。虽然它又快又小,但它的正常应用是健全性检查。如果 2^32 选项就足够了,那么 CRC32 就可以了(或者显然 Python 中的 hash() 函数也会生成 2^32。不知道这个,我并没有真正使用 Python)
  • 刚刚在 python 中做了一个快速的分析测试。 binascii.crc32 1600 万个哈希值需要 7.05 秒,而 hash 函数需要 5.59 秒。 Python 可能正在缓存字符串的哈希值——我不确定。 Python 的内置哈希函数是使用 SipHash 构建的,它应该在一定程度上防止有目的的冲突。缺点是该函数使用随机数作为种子,以防止有目的的冲突,并且从两个不同的进程运行会产生不同的结果。这可以通过指定 PYTHONHASHSEED 环境变量来覆盖。
【解决方案5】:

如果确实发生了碰撞,如何判断它确实发生了?

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

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

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

【讨论】:

  • 我解开缓存并注意到什么时候出了问题,所以碰撞只是两个碰撞对象的麻烦,一个总是在应用程序启动时没有缓存。但这是一个非常好的建议,因为 sha1 是不会发生太多冲突的哈希函数类型,而在哈希中切片是我没有考虑过的。
  • 实际上,由于各种数学原因,使用散列的子字符串会产生比仅使用较短的散列函数更多的冲突。请参阅作为协议一部分实时生成部分 SHA1 冲突的示例协议。
  • 过去,我们采用 MD5 的 1/2,将其转换为 64 位整数,并将其存储在数据库中(在这种情况下,性能至关重要,记录超过 100,000,000 条。跨度>
  • @Matthew Scharley:您是否有任何指向该信息的链接——我很感兴趣。
【解决方案6】:

我们使用 hashlib.sha1.hexdigest(),它产生更长的字符串,成功地缓存对象。无论如何,实际上没有人在查看缓存文件。

【讨论】:

    【解决方案7】:

    考虑到您的用例,如果您不打算使用单独的缓存文件,并且您的开发路径不太远,您可以考虑使用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
    

    【讨论】:

      【解决方案8】:

      短哈希意味着您可能对两个不同的文件有相同的哈希。大哈希也可能发生同样的情况,但这种情况更为罕见。 也许这些文件名应该根据其他参考而有所不同,例如 microtime(除非这些文件可能创建得太快)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-07-29
        • 1970-01-01
        • 2011-11-10
        • 1970-01-01
        • 2013-04-28
        • 2013-07-29
        • 2016-04-11
        • 1970-01-01
        相关资源
        最近更新 更多