5.9 读取二进制数据到可变缓冲区中

问题

你想直接读取二进制数据到一个可变缓冲区中,而不需要做任何的中间复制操作。或者你想原地修改数据并将它写回到一个文件中去。

解决方案

为了读取数据到一个可变数组中,使用文件对象的 readinto() 方法。比如:

  1. import os.path
  2.  
  3. def read_into_buffer(filename):
  4. buf = bytearray(os.path.getsize(filename))
  5. with open(filename, 'rb') as f:
  6. f.readinto(buf)
  7. return buf

下面是一个演示这个函数使用方法的例子:

  1. >>> # Write a sample file
  2. >>> with open('sample.bin', 'wb') as f:
  3. ... f.write(b'Hello World')
  4. ...
  5. >>> buf = read_into_buffer('sample.bin')
  6. >>> buf
  7. bytearray(b'Hello World')
  8. >>> buf[0:5] = b'Hello'
  9. >>> buf
  10. bytearray(b'Hello World')
  11. >>> with open('newsample.bin', 'wb') as f:
  12. ... f.write(buf)
  13. ...
  14. 11
  15. >>>

讨论

文件对象的 readinto() 方法能被用来为预先分配内存的数组填充数据,甚至包括由 array 模块或 numpy 库创建的数组。和普通 read() 方法不同的是, readinto() 填充已存在的缓冲区而不是为新对象重新分配内存再返回它们。因此,你可以使用它来避免大量的内存分配操作。比如,如果你读取一个由相同大小的记录组成的二进制文件时,你可以像下面这样写:

  1. record_size = 32 # Size of each record (adjust value)
  2.  
  3. buf = bytearray(record_size)
  4. with open('somefile', 'rb') as f:
  5. while True:
  6. n = f.readinto(buf)
  7. if n < record_size:
  8. break
  9. # Use the contents of buf
  10. ...

另外有一个有趣特性就是 memoryview ,它可以通过零复制的方式对已存在的缓冲区执行切片操作,甚至还能修改它的内容。比如:

  1. >>> buf
  2. bytearray(b'Hello World')
  3. >>> m1 = memoryview(buf)
  4. >>> m2 = m1[-5:]
  5. >>> m2
  6. <memory at 0x100681390>
  7. >>> m2[:] = b'WORLD'
  8. >>> buf
  9. bytearray(b'Hello WORLD')
  10. >>>

使用 f.readinto() 时需要注意的是,你必须检查它的返回值,也就是实际读取的字节数。

如果字节数小于缓冲区大小,表明数据被截断或者被破坏了(比如你期望每次读取指定数量的字节)。

最后,留心观察其他函数库和模块中和 into 相关的函数(比如 recv_into()pack_into() 等)。Python的很多其他部分已经能支持直接的I/O或数据访问操作,这些操作可被用来填充或修改数组和缓冲区内容。

关于解析二进制结构和 memoryviews 使用方法的更高级例子,请参考6.12小节。

原文:

http://python3-cookbook.readthedocs.io/zh_CN/latest/c05/p09_read_binary_data_into_mutable_buffer.html