【发布时间】:2018-12-28 01:27:33
【问题描述】:
在 Python 中,我想快速计算文件行的顺序不变散列,作为“唯一”识别其内容的一种方式。例如,这些文件是 select ... from table 的输出,因此行的顺序是随机的。
这是一个实现我想要的示例(使用 hashlib 中的哈希器之一),但代价是必须对行进行排序。 请注意,对行进行排序只是实现目标的一种方式,即获得一个不依赖于文件中行顺序的哈希。但很明显,我想避免 O(n*log(n)) 成本,尤其是。当文件更长时。
def get_hexdigest(filename, hasher, blocksize=65536, order_invariant=False):
if not os.path.isfile(filename):
return None
if order_invariant:
with open(filename, 'r') as f:
for line in sorted(f):
hasher.update(line.encode())
else:
with open(filename, 'rb') as f:
while True:
buf = f.read(blocksize)
hasher.update(buf)
if len(buf) < blocksize:
break
return hasher.hexdigest()
所以,例如1MB,50K 行文件:
%%time
get_hexdigest('some_file', hashlib.sha1())
# Wall time: 1.71 ms
但是:
%%time
get_hexdigest('some_file', hashlib.sha1(), order_invariant=True)
# Wall time: 77.4 ms
有什么更好/更快的方法来做到这一点?
正如in this answer 所指出的,Scala 有一个基于 Murmurhash 的顺序不变哈希,但我认为它是 mmh3 的 32 位版本(对我的使用来说太容易发生冲突),而且我宁愿使用一些标准Python 中可用的库,而不是在 C 或 Cython 中实现某些东西。 Murmurhash3 有一个 128 位版本,但它的输出在 x64 和 x86 上是不同的。我想获得独立于机器的结果。
所以,总而言之,我想:
- 跨机器架构的一致结果
- 低冲突率,即至少 128 位具有良好的分散性(但我不需要哈希是加密的)
- 相当快,即 1MB、50K 行的文件至少在 5ms 以下。
- 如果可能的话,作为 PyPi 或 Conda 上的库随时可用。
- 适用于具有重复行的文件(因此,仅对每行哈希进行异或运算是不可能的,因为任何一对相同的行都会相互抵消)。
编辑和注释:
多亏了几个 cmets,上面的代码更新为对内存中的行进行排序。 order_invariant is True 的原始版本是:
with os.popen('sort {}'.format(filename)) as f:
for line in f:
hasher.update(line.encode(encoding='utf-8'))
return hasher.hexdigest()
相关的挂墙时间(对于上面使用的文件)是 238 毫秒。这现在减少到 77 毫秒,但仍然比不对行进行排序要慢得多。排序将为 n 行添加 n*log(n) 成本。
在读取行时,编码(到 UTF-8)和读取模式 'r' 或 'rb' 是必要的,因为这样我们得到的是字符串而不是字节。我不想依赖假设文件只包含 ASCII 数据。读入'rb' 可能会导致行未正确拆分。当order_invariant 为 False 时,我没有同样的顾虑,因为这样我就不必拆分文件,因此最快的方法是使用大块的二进制数据来更新哈希。
【问题讨论】:
-
第一个改进:您可能可以使用
for lines in sorted(f)来sortpython 行而不是调用外部进程... -
附带说明,如果您正在使用行(基于 'order-invariant hash for the lines of a file'),为什么要在“订单敏感”案例?只需用
.readline()输出hasher。 -
另外:对整个文件进行编码并然后拆分行,然后在每一行上调用
.encode:with open(...) as f: for line in sorted(f.read().encode('utf-8').split('\n')): hasher.update(line)(是的,它加载整个文件到内存中,但排序需要这个)。 -
或者更好的是,以二进制模式打开文件,这样就不会发生解码/重新编码
-
@ThierryLathuille - 他的最小散列单位是行,因此将整个文件读入缓冲区是一个有争议的问题。再说一次,出于某种原因,他也以二进制模式打开文件,几乎可以保证上述内容不会为两个完全相同的文件返回相同的哈希值,而在第一种情况下,他甚至没有逐行散列.