Get MD5 hash of big files in Python
我使用了hashlib(它取代了Python 2.6 / 3.0中的md5),如果我打开一个文件并将其内容放在
问题在于非常大的文件,它们的大小可能超过RAM大小。
如何在不将整个文件加载到内存的情况下获取文件的MD5哈希值?
您需要以合适大小的块读取文件:
1 2 3 4 5 6 7 8 | def md5_for_file(f, block_size=2**20): md5 = hashlib.md5() while True: data = f.read(block_size) if not data: break md5.update(data) return md5.digest() |
注意:确保打开文件时将"rb"打开 - 否则会得到错误的结果。
所以要用一种方法完成所有操作 - 使用类似的东西:
1 2 3 4 5 6 7 8 9 | def generate_file_md5(rootdir, filename, blocksize=2**20): m = hashlib.md5() with open( os.path.join(rootdir, filename) ,"rb" ) as f: while True: buf = f.read(blocksize) if not buf: break m.update( buf ) return m.hexdigest() |
上面的更新是基于Frerich Raabe提供的评论 - 我测试了这一点并发现它在我的Python 2.7.2 Windows安装上是正确的
我使用'jacksum'工具交叉检查结果。
1 | jacksum -a md5 <filename> |
http://www.jonelo.de/java/jacksum/
将文件分成128个字节的块,然后使用
这利用了MD5具有128字节摘要块的事实。基本上,当MD5
如果确保在每次迭代时释放内存(即不将整个文件读取到内存中),则不应超过128个字节的内存。
一个例子是像这样读取块:
1 2 3 | f = open(fileName) while not endOfFile: f.read(128) |
如果您关心更多pythonic(没有'while True')阅读文件的方式,请检查以下代码:
1 2 3 4 5 6 7 8 | import hashlib def checksum_md5(filename): md5 = hashlib.md5() with open(filename,'rb') as f: for chunk in iter(lambda: f.read(8192), b''): md5.update(chunk) return md5.digest() |
请注意,iter()func需要一个空的字节字符串,以便返回的迭代器在EOF处停止,因为read()返回b''(不仅仅是'')。
这是我的@Piotr Czapla方法的版本:
1 2 3 4 5 6 | def md5sum(filename): md5 = hashlib.md5() with open(filename, 'rb') as f: for chunk in iter(lambda: f.read(128 * md5.block_size), b''): md5.update(chunk) return md5.hexdigest() |
在这个帖子中使用多个评论/答案,这是我的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import hashlib def md5_for_file(path, block_size=256*128, hr=False): ''' Block size directly depends on the block size of your filesystem to avoid performances issues Here I have blocks of 4096 octets (Default NTFS) ''' md5 = hashlib.md5() with open(path,'rb') as f: for chunk in iter(lambda: f.read(block_size), b''): md5.update(chunk) if hr: return md5.hexdigest() return md5.digest() |
- 这是"pythonic"
- 这是一个功能
- 它避免了隐含的值:总是更喜欢显式值。
- 它允许(非常重要的)性能优化
最后,
- 这是由社区建立的,感谢您的建议/想法。
Python 2/3便携式解决方案
要计算校验和(md5,sha1等),必须以二进制模式打开文件,因为您将对字节值求和:
要成为py27 / py3便携式,你应该使用
1 2 3 4 5 6 7 8 9 10 | import hashlib import io def md5sum(src): md5 = hashlib.md5() with io.open(src, mode="rb") as fd: content = fd.read() md5.update(content) return md5 |
如果您的文件很大,您可能更喜欢按块读取文件,以避免将整个文件内容存储在内存中:
1 2 3 4 5 6 | def md5sum(src, length=io.DEFAULT_BUFFER_SIZE): md5 = hashlib.md5() with io.open(src, mode="rb") as fd: for chunk in iter(lambda: fd.read(length), b''): md5.update(chunk) return md5 |
这里的技巧是使用带有sentinel的
The iterator created in this case will call o [the lambda function] with no arguments for each call to its
next() method; if the value returned is equal to sentinel,StopIteration will be raised, otherwise the value will be returned.
如果您的文件非常大,您可能还需要显示进度信息。您可以通过调用打印或记录计算字节数量的回调函数来实现:
1 2 3 4 5 6 7 8 9 | def md5sum(src, callback, length=io.DEFAULT_BUFFER_SIZE): calculated = 0 md5 = hashlib.md5() with io.open(src, mode="rb") as fd: for chunk in iter(lambda: fd.read(length), b''): md5.update(chunk) calculated += len(chunk) callback(calculated) return md5 |
Bastien Semene代码的重新混合,考虑了Hawkwing关于泛型散列函数的评论......
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | def hash_for_file(path, algorithm=hashlib.algorithms[0], block_size=256*128, human_readable=True): """ Block size directly depends on the block size of your filesystem to avoid performances issues Here I have blocks of 4096 octets (Default NTFS) Linux Ext4 block size sudo tune2fs -l /dev/sda5 | grep -i 'block size' > Block size: 4096 Input: path: a path algorithm: an algorithm in hashlib.algorithms ATM: ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512') block_size: a multiple of 128 corresponding to the block size of your filesystem human_readable: switch between digest() or hexdigest() output, default hexdigest() Output: hash """ if algorithm not in hashlib.algorithms: raise NameError('The algorithm"{algorithm}" you specified is ' 'not a member of"hashlib.algorithms"'.format(algorithm=algorithm)) hash_algo = hashlib.new(algorithm) # According to hashlib documentation using new() # will be slower then calling using named # constructors, ex.: hashlib.md5() with open(path, 'rb') as f: for chunk in iter(lambda: f.read(block_size), b''): hash_algo.update(chunk) if human_readable: file_hash = hash_algo.hexdigest() else: file_hash = hash_algo.digest() return file_hash |
如果没有阅读完整内容,你就无法获得它的md5。但你可以使用更新功能逐块读取文件内容。
m.update(一); m.update(b)相当于m.update(a + b)
我认为以下代码更pythonic:
1 2 3 4 5 6 7 8 | from hashlib import md5 def get_md5(fname): m = md5() with open(fname, 'rb') as fp: for chunk in fp: m.update(chunk) return m.hexdigest() |
我不喜欢循环。基于@Nathan Feger:
1 2 3 4 | md5 = hashlib.md5() with open(filename, 'rb') as f: functools.reduce(lambda _, c: md5.update(c), iter(lambda: f.read(md5.block_size * 128), b''), None) md5.hexdigest() |
为Django实现接受的答案:
1 2 3 4 5 6 7 8 9 10 11 12 | import hashlib from django.db import models class MyModel(models.Model): file = models.FileField() # any field based on django.core.files.File def get_hash(self): hash = hashlib.md5() for chunk in self.file.chunks(chunk_size=8192): hash.update(chunk) return hash.hexdigest() |
1 2 3 4 5 6 7 8 9 | import hashlib,re opened = open('/home/parrot/pass.txt','r') opened = open.readlines() for i in opened: strip1 = i.strip(' ') hash_object = hashlib.md5(strip1.encode()) hash2 = hash_object.hexdigest() print hash2 |
我不确定周围是不是有太多的烦恼。我最近遇到了md5和在MySQL上存储为blob的文件的问题所以我尝试了各种文件大小和简单的Python方法,即:
1 | FileHash=hashlib.md5(FileData).hexdigest() |
我发现在2Kb到20Mb的文件大小范围内没有明显的性能差异,因此无需"散列"散列。无论如何,如果Linux必须转向磁盘,它可能至少与普通程序员保持这样做的能力一样。碰巧的是,问题与md5无关。如果您正在使用MySQL,请不要忘记已存在的md5()和sha1()函数。