常规键是您要迭代处理文件。
如果您只处理文本文件,那么这很简单:一次
for line in f:只能读取一行。(实际上,它可以缓冲所有内容,但是缓冲区足够小,您不必担心。)
如果您要处理其他特定文件类型(例如numpy二进制文件,CSV文件,XML文档等),则通常会有类似的特殊用途解决方案,但是除非您告诉我们什么,否则没人能向您描述它们您拥有的那种数据。
但是,如果您有通用的二进制文件怎么办?
首先,该
read方法需要读取一个可选的最大字节。因此,代替此:
data = f.read()process(data)
你可以这样做:
while True: data = f.read(8192) if not data: break process(data)
您可能需要编写如下函数:
def chunks(f): while True: data = f.read(8192) if not data: break yield data
然后,您可以执行以下 *** 作:
for chunk in chunks(f): process(chunk)
您也可以使用两个参数来执行此 *** 作
iter,但是许多人发现它有点晦涩:
for chunk in iter(partial(f.read, 8192), b''): process(chunk)
无论哪种方式,此选项都适用于下面的所有其他变体(单个除外
mmap,这很琐碎,没有意义)。
那里的8192数字没有什么神奇的。通常,您确实希望2的幂,并且最好是系统页面大小的倍数。除此之外,无论您使用的是4KB还是4MB的性能,其性能都不会有太大变化。如果是,则必须测试哪种方法最适合您的用例。
无论如何,这假设您一次可以处理每个8K,而无需保留任何上下文。例如,如果您要将数据馈送到渐进式解码器或哈希器等中,那是完美的。
但是,如果您需要一次处理一个“块”,那么您的块最终可能会跨越8K边界。你怎么处理那件事呢?
这取决于如何在文件中分隔块,但是基本思路非常简单。例如,假设您使用NUL字节作为分隔符(不太可能,但很容易显示为玩具示例)。
data = b''while True: buf = f.read(8192) if not buf: process(data) break data += buf chunks = data.split(b'') for chunk in chunks[:-1]: process(chunk) data = chunks[-1]
这种代码在网络中非常常见(因为
sockets不能 只是“全部读取”,因此您 总是
必须读入缓冲区并分块放入消息中),因此在网络代码中可能会找到一些有用的示例,这些示例使用类似的协议为您的文件格式。
或者,您可以使用
mmap。
如果您的虚拟内存大小大于文件大小,那么这很简单:
with mmap.mmap(f.fileno(), access=mmap.ACCESS_READ) as m: process(m)
现在,
m它就像一个巨大的
bytes对象,就像您被调用
read()来将整个内容读入内存一样,但是 *** 作系统将根据需要自动将位分页移入和移出内存。
如果您尝试读取的文件太大而无法容纳您的虚拟内存大小(例如,具有32位Python的4GB文件或具有64位Python的20EB文件),那么只有在2013年,如果要读取稀疏或虚拟文件(例如Linux上另一个进程的VM文件),则必须实现窗口化-
一次在文件的一部分中实现mmap。例如:
windowsize = 8*1024*1024size = os.fstat(f.fileno()).st_sizefor start in range(0, size, window size): with mmap.mmap(f.fileno(), access=mmap.ACCESS_READ, length=windowsize, offset=start) as m: process(m)
当然,如果需要定界,则映射窗口与读取块具有相同的问题,并且可以用相同的方法解决。
但是,作为一种优化,而不是缓冲,您可以将窗口向前滑动到包含最后一条完整消息末尾的页面,而不是一次滑动8MB,然后可以避免任何复制。这有点复杂,因此,如果要执行此 *** 作,请搜索“滑动mmap窗口”之类的内容,并在遇到问题时写一个新问题。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)