【问题标题】:Fast hash for strings字符串的快速哈希
【发布时间】:2014-03-26 20:05:21
【问题描述】:

我有一组 ASCII 字符串,假设它们是文件路径。它们可以很短也可以很长。

我正在寻找一种可以计算此类字符串散列的算法,并且此散列也是一个字符串,但具有固定长度,例如 youtube 视频 ID:

https://www.youtube.com/watch?v=-F-3E8pyjFo
                                ^^^^^^^^^^^

MD5 似乎是我需要的,但对我来说,拥有一个简短的哈希字符串很关键。

是否有可以做到这一点的 shell 命令或 python 库?

【问题讨论】:

  • 你的意思是除了标准的md5 模块? (但已弃用;现在您可以改用 hashlib
  • 问题更多的是算法而不是实现
  • 没有碰撞对您来说有多重要,速度有多重要?与其他算法相比,MD5 实际上不是很快,也不是很短。您可以使用生日悖论公式计算碰撞风险(参见维基百科)。

标签: python bash algorithm hash hashids


【解决方案1】:

Python 有一个内置的 hash() 函数,它非常快速且非常适合大多数用途:

>>> hash("dfds")
3591916071403198536

然后您可以将其设为无符号:

>>> hashu=lambda word: ctypes.c_uint64(hash(word)).value

然后你可以把它变成一个 16 字节的十六进制字符串:

>>> hashu("dfds").to_bytes(8,"big").hex()

或一个 N*2 字节的字符串,其中 N 为

>>> hashn=lambda word, N  : (hashu(word)%(2**(N*8))).to_bytes(N,"big").hex()

..等等。如果你想让 N 大于 8 个字节,你可以散列两次。 Python 的内置速度如此之快,除非您需要安全性,否则永远不值得将 hashlib 用于任何事情……而不仅仅是防碰撞。

>>> hashnbig=lambda word, N  : ((hashu(word)+2**64*hashu(word+"2"))%(2**(N*8))).to_bytes(N,"big").hex()

最后,使用 urlsafe base64 编码制作比“hex”更好的字符串

>>> hashnbigu=lambda word, N  : urlsafe_b64encode(((hashu(word)+2**64*hash(word+"2"))%(2**(N*8))).to_bytes(N,"big")).decode("utf8").rstrip("=")
>>> hashnbigu("foo",16)
'ZblnvrRqHwAy2lnvrR4HrA'

注意事项:

  • 请注意,在 Python 3.3 及更高版本中,此函数是 随机,不适用于某些用例。您可以使用 PYTHONHASHSEED=0

    禁用此功能
  • 请参阅https://github.com/flier/pyfasthash 了解快速、稳定的哈希值 不会破坏您的 CPU 用于非加密应用程序。

  • 不要在实际代码中使用这种 lambda 样式...写出来!和 在代码中填充 2**32 之类的东西,而不是制作它们 常量是不好的形式。

  • 最后 8 字节的抗碰撞性对于较小的来说是可以的 应用程序......少于一百万个条目,你有 碰撞几率

  • 对于缓存中的 UUID/OID 等而言,16 个字节就足够了。

【讨论】:

  • 在 Python 3 中这个函数是随机的,在某些情况下这可能是个问题。
  • 感谢@Tim,来自文档:默认情况下,strbytesdatetime 对象的 __hash__() 值是用不可预测的随机值“加盐”的;设置环境变量 PYTHONHASHSEED=0 以禁用随机化 [...] 以允许 python 进程集群共享哈希值。
  • hash('asd').to_bytes(8, 'little') OverflowError: can't convertnegative int to unsigned
  • @iperov fixed ... 将,signed=True 传递给to_bytes
  • @iperov 最好使散列无符号。 ctypes 似乎是唯一干净的方法。
【解决方案2】:

我猜这个问题是题外话,因为基于意见,但至少给你一个提示,我知道FNV hash,因为它被 The Sims 3 用来查找基于它们在不同内容包之间的名称。他们使用 64 位版本,所以我想这足以避免在相对较大的一组参考字符串中发生冲突。 The hash is easy to implement,如果没有满足您的模块(例如,pyfasthash 有它的实现)。

为了得到一个短字符串,我建议你使用 base64 编码。例如,这是 base64 编码的 64 位哈希的大小:nsTYVQUag88=(您可以去掉或填充 =)。

编辑:我终于遇到了和你一样的问题,所以实现了上面的思路:https://gist.github.com/Cilyan/9424144

【讨论】:

  • FNV 是迄今为止我最喜欢的哈希值。
【解决方案3】:

另一种选择:hashids 旨在解决这个问题,并已被移植到包括 Python 在内的多种语言中。在 MD5 或 SHA1 的意义上,它并不是真正的哈希,它们是单向的; hashids“哈希”是可逆的。

您负责使用秘密值播种库并选择最小散列长度。

完成后,该库可以在整数(单个整数,如简单的主键,或整数列表,以支持复合键和分片等)和配置长度的字符串(或微微多一点)。用于生成“哈希”的字母表是完全可配置的。

我已经在this other answer提供了更多细节。

【讨论】:

    【解决方案4】:

    您可以使用sum 程序(假设您使用的是Linux),但请记住,哈希越短,您可能遇到的冲突就越多。您也可以随时截断 MD5/SHA 哈希。

    编辑:这是哈希函数列表:List of hash functions

    【讨论】:

    • 这里有介绍:link
    【解决方案5】:

    需要记住的是,哈希码是单向函数 - 您不能将它们用于“视频 ID”,因为您无法从哈希码返回到原始路径。除了其他任何事情之外,很可能发生哈希冲突,并且您最终会得到两个哈希都指向同一个视频而不是不同的视频。

    要创建一个像 youtube 一样的 ID,最简单的方法是创建一个唯一的 ID,但是您通常会这样做(例如数据库中的自动键列),然后以可逆的方式将其映射到唯一的字符串。

    例如,您可以获取一个整数 id 并将其映射到以 36 为基数的 0-9a-z...甚至以 62 为基数的 0-9a-zA-Z,如果id 本身不能提供足够的字符。

    【讨论】:

      猜你喜欢
      • 2012-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-27
      • 1970-01-01
      • 1970-01-01
      • 2013-09-12
      • 2013-01-20
      相关资源
      最近更新 更多