从文件对象中读取块,直到从末尾开始 x 个字节

Read blocks from a file object until x bytes from the end

我需要循环读取 64KB 的块,并处理它们,但是 在文件末尾减去 16 字节后停止:最后 16 字节是 tag元数据。

文件可能超大,所以我无法在 RAM 中全部读取。

我找到的所有解决方案都有点笨拙and/or unpythonic。

with open('myfile', 'rb') as f:
    while True:
        block = f.read(65536)
        if not block:
            break
        process_block(block)

如果16 <= len(block) < 65536,很简单:这是有史以来的最后一个区块。所以 useful_data = block[:-16]tag = block[-16:]

如果len(block) == 65536,它可能意味着三件事:整个块都是有用的数据。或者这个 64KB 块实际上是 最后一个块 ,所以 useful_data = block[:-16]tag = block[-16:]。或者这个 64KB 的块后面跟着另一个只有几个字节的块(假设是 3 个字节),所以在这种情况下:useful_data = block[:-13]tag = block[-13:] + last_block[:3].

如何以比区分所有这些情况更好的方式处理这个问题?

注:

你可以在每次迭代中阅读 min(65536, L-f.tell()-16)

像这样:

from pathlib import Path

L = Path('myfile').stat().st_size

with open('myfile', 'rb') as f:
    while True:    
        to_read_length = min(65536, L-f.tell()-16)
        block = f.read(to_read_length)
        process_block(block)
        if f.tell() == L-16
            break

没有运行这个,但希望你能理解它的要点。

以下方法仅依赖于 f.read() 方法 returns 流结束时的空字节对象 (EOS) 这一事实。因此,只需将 f.read() 替换为 s.recv().

即可将其用于套接字
def read_all_but_last16(f):
    rand = random.Random()  #  just for testing
    buf = b''
    while True:
        bytes_read = f.read(rand.randint(1, 40))  # just for testing
        # bytes_read = f.read(65536)
        buf += bytes_read
        if not bytes_read:
            break
        process_block(buf[:-16])
        buf = buf[-16:]
    verify(buf[-16:])

它的工作原理是始终在 buf 末尾保留 16 个字节,直到 EOS,然后最后处理最后 16 个字节。请注意,如果 buf 中没有至少 17 个字节,则 buf[:-16] returns 空字节对象。