Reading binary file and looping over each byte
在Python中,如何读取二进制文件并循环遍历该文件的每个字节?
Python 2.4及更早版本
1 2 3 4 5 6 7 8 | f = open("myfile","rb") try: byte = f.read(1) while byte !="": # Do stuff with byte. byte = f.read(1) finally: f.close() |
Python 2.5-2.7
1 2 3 4 5 | with open("myfile","rb") as f: byte = f.read(1) while byte !="": # Do stuff with byte. byte = f.read(1) |
请注意,with语句在2.5以下的Python版本中不可用。要在v 2.5中使用它,您需要导入它:
1 | from __future__ import with_statement |
在2.6中,这不是必需的。
Python 3
在Python 3中,它有点不同。我们将不再以字节模式从流中获取原始字符,而是字节对象,因此我们需要更改条件:
1 2 3 4 5 | with open("myfile","rb") as f: byte = f.read(1) while byte != b"": # Do stuff with byte. byte = f.read(1) |
或者正如benhoyt所说,跳过不等于并利用
1 2 3 4 5 | with open("myfile","rb") as f: byte = f.read(1) while byte: # Do stuff with byte. byte = f.read(1) |
此生成器从文件中生成字节,以块的形式读取文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 | def bytes_from_file(filename, chunksize=8192): with open(filename,"rb") as f: while True: chunk = f.read(chunksize) if chunk: for b in chunk: yield b else: break # example: for b in bytes_from_file('filename'): do_stuff_with(b) |
有关迭代器和生成器的信息,请参阅Python文档。
如果文件不是太大,将其保存在内存中就是一个问题:
1 2 3 | bytes_read = open("filename","rb").read() for b in bytes_read: process_byte(b) |
其中process_byte表示要对传入的字节执行的某些操作。
如果要一次处理一个块:
1 2 3 4 5 6 7 8 9 | file = open("filename","rb") try: bytes_read = file.read(CHUNKSIZE) while bytes_read: for b in bytes_read: process_byte(b) bytes_read = file.read(CHUNKSIZE) finally: file.close() |
要读取文件 - 一次一个字节(忽略缓冲) - 您可以使用双参数
1 2 3 | with open(filename, 'rb') as file: for byte in iter(lambda: file.read(1), b''): # Do stuff with byte |
它调用
尽管默认情况下存在内部缓冲,但一次处理一个字节仍然是低效的。例如,这是
1 2 3 4 5 6 7 8 | #!/usr/bin/env python3 """Discard all input. `cat > /dev/null` analog.""" import sys from functools import partial from collections import deque chunksize = int(sys.argv[1]) if len(sys.argv) > 1 else (1 << 15) deque(iter(partial(sys.stdin.detach().read, chunksize), b''), maxlen=0) |
例:
1 | $ dd if=/dev/zero bs=1M count=1000 | python3 blackhole.py |
它在我的机器上
1 2 3 4 5 | from mmap import ACCESS_READ, mmap with open(filename, 'rb', 0) as f, mmap(f.fileno(), 0, access=ACCESS_READ) as s: for byte in s: # length is equal to the current file size # Do stuff with byte |
总结一下chrispy,Skurmedel,Ben Hoyt和Peter Hansen的所有优点,这将是一次一个字节处理二进制文件的最佳解决方案:
1 2 3 4 5 6 | with open("myfile","rb") as f: while True: byte = f.read(1) if not byte: break do_stuff_with(ord(byte)) |
对于Python 2.6及更高版本,因为:
- 内部的python缓冲区 - 无需读取块
- 干燥原理 - 不要重复读取线
- with语句确保关闭文件
- 当没有更多字节时(不是字节为零时),'byte'的计算结果为false
或者使用J. F. Sebastians解决方案来提高速度
1 2 3 4 5 | from functools import partial with open(filename, 'rb') as file: for byte in iter(partial(file.read, 1), b''): # Do stuff with byte |
或者如果你想将它作为代码表示的生成器函数:
1 2 3 4 5 6 7 8 9 10 11 | def bytes_from_file(filename): with open(filename,"rb") as f: while True: byte = f.read(1) if not byte: break yield(ord(byte)) # example: for b in bytes_from_file('filename'): do_stuff_with(b) |
Reading binary file in Python and looping over each byte
Python 3.5中的新功能是
1 2 3 4 | import pathlib for byte in pathlib.Path(path).read_bytes(): print(byte) |
有趣的是,这是提及
在Python 2中,你可能会这样做(正如Vinay Sajip也建议的那样):
1 2 3 | with open(path, 'b') as file: for byte in file.read(): print(byte) |
如果文件可能太大而无法在内存中进行迭代,那么您可以使用带有
1 2 3 4 5 6 | with open(path, 'b') as file: callable = lambda: file.read(1024) sentinel = bytes() # or b'' for chunk in iter(callable, sentinel): for byte in chunk: print(byte) |
(其他几个答案提到了这一点,但很少有人提供合理的读取大小。)
大文件或缓冲/交互式阅读的最佳实践
让我们创建一个执行此操作的函数,包括Python 3.5+标准库的惯用用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from pathlib import Path from functools import partial from io import DEFAULT_BUFFER_SIZE def file_byte_iterator(path): """given a path, return an iterator over the file that lazily loads the file """ path = Path(path) with path.open('rb') as file: reader = partial(file.read1, DEFAULT_BUFFER_SIZE) file_iterator = iter(reader, bytes()) for chunk in file_iterator: for byte in chunk: yield byte |
请注意,我们使用
演示最佳实践用法:
让我们创建一个具有兆字节(实际上是mebibyte)的伪随机数据的文件:
1 2 3 4 5 6 7 | import random import pathlib path = 'pseudorandom_bytes' pathobj = pathlib.Path(path) pathobj.write_bytes( bytes(random.randint(0, 255) for _ in range(2**20))) |
现在让我们迭代它并在内存中实现它:
1 2 3 | >>> l = list(file_byte_iterator(path)) >>> len(l) 1048576 |
我们可以检查数据的任何部分,例如,最后100个字节和前100个字节:
1 2 3 4 | >>> l[-100:] [208, 5, 156, 186, 58, 107, 24, 12, 75, 15, 1, 252, 216, 183, 235, 6, 136, 50, 222, 218, 7, 65, 234, 129, 240, 195, 165, 215, 245, 201, 222, 95, 87, 71, 232, 235, 36, 224, 190, 185, 12, 40, 131, 54, 79, 93, 210, 6, 154, 184, 82, 222, 80, 141, 117, 110, 254, 82, 29, 166, 91, 42, 232, 72, 231, 235, 33, 180, 238, 29, 61, 250, 38, 86, 120, 38, 49, 141, 17, 190, 191, 107, 95, 223, 222, 162, 116, 153, 232, 85, 100, 97, 41, 61, 219, 233, 237, 55, 246, 181] >>> l[:100] [28, 172, 79, 126, 36, 99, 103, 191, 146, 225, 24, 48, 113, 187, 48, 185, 31, 142, 216, 187, 27, 146, 215, 61, 111, 218, 171, 4, 160, 250, 110, 51, 128, 106, 3, 10, 116, 123, 128, 31, 73, 152, 58, 49, 184, 223, 17, 176, 166, 195, 6, 35, 206, 206, 39, 231, 89, 249, 21, 112, 168, 4, 88, 169, 215, 132, 255, 168, 129, 127, 60, 252, 244, 160, 80, 155, 246, 147, 234, 227, 157, 137, 101, 84, 115, 103, 77, 44, 84, 134, 140, 77, 224, 176, 242, 254, 171, 115, 193, 29] |
不要按行迭代二进制文件
不要执行以下操作 - 这会拉出任意大小的块直到它到达换行符 - 当块太小时太慢,并且可能太大:
1 2 3 4 | with open(path, 'rb') as file: for chunk in file: # text newline iteration - not for bytes for byte in chunk: yield byte |
以上只适用于语义上人类可读的文本文件(如纯文本,代码,标记,降价等...基本上任何ascii,utf,latin等编码)。
在尝试了以上所有内容并使用@Aaron Hall的答案后,我在运行Window 10,8 Gb RAM和Python 3.5 32位的计算机上获得了大约90 Mb文件的内存错误。我被一位同事推荐使用
到目前为止,读取整个二进制文件(我测试过的)最快的是:
1 2 3 4 | import numpy as np file ="binary_file.bin" data = np.fromfile(file, 'u1') |
参考
到目前为止,比任何其他方法都要快。希望它可以帮到某人!
Python 3,一次读取所有文件:
1 2 3 4 | with open("filename","rb") as binary_file: # Read the whole file at once data = binary_file.read() print(data) |
您可以使用
如果要读取大量二进制数据,则可能需要考虑struct模块。它被记录为转换"在C和Python类型之间",但当然,字节是字节,并且这些是否被创建为C类型并不重要。例如,如果二进制数据包含两个2字节整数和一个4字节整数,则可以按如下方式读取它们(示例来自
1 2 | >>> struct.unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03') (1, 2, 3) |
您可能会发现这比显式循环文件内容更方便,更快捷或两者兼而有之。
如果你正在寻找快速的东西,这是我一直在使用的方法多年来一直工作:
1 2 3 4 5 6 7 8 9 | from array import array with open( path, 'rb' ) as file: data = array( 'B', file.read() ) # buffer the file # evaluate it's data for byte in data: v = byte # int value c = chr(byte) |
如果你想迭代字符而不是整数,你可以简单地使用