将整个二进制文件读入 Python

Posted

技术标签:

【中文标题】将整个二进制文件读入 Python【英文标题】:Reading an entire binary file into Python 【发布时间】:2010-12-12 19:47:04 【问题描述】:

我需要从 Python 导入一个二进制文件——内容是有符号的 16 位整数,大端。

以下 Stack Overflow 问题建议如何一次提取多个字节,但这是扩大读取整个文件的方法吗?

Reading some binary file in Python

Receiving 16-bit integers in Python

我想创建一个类似的函数:

from numpy import *
import os

def readmyfile(filename, bytes=2, endian='>h'):
    totalBytes = os.path.getsize(filename)
    values = empty(totalBytes/bytes)
    with open(filename, 'rb') as f:
        for i in range(len(values)):
            values[i] = struct.unpack(endian, f.read(bytes))[0]
    return values

filecontents = readmyfile('filename')

但这很慢(文件为 165924350 字节)。有没有更好的办法?

【问题讨论】:

我认为它很慢,因为bytes=2 读取 150mb 的文件会很慢。你能指望什么?有多慢? 实际上只有大约 3.5 分钟(根据 unix time),但我可以使用 readBin 在不到一分钟的时间内将其读入 R (我有数千个这样的文件...) 数据是明确的二进制还是16位数字的ASCII表示? 【参考方案1】:

使用numpy.fromfile

【讨论】:

fromfile(filename,dtype='>i2')这么简单吗? @Stephen,是的,这就是你需要做的。如果 Karl 把它放在答案中,那么这是最好和最简单的答案。 根据我的经验 numpy.fromfile 非常快且非常易于使用。【参考方案2】:

我会直接读取直到 EOF(这意味着检查是否接收到空字符串),然后不再需要使用 range() 和 getsize。 或者,使用xrange(而不是range)应该会有所改善,尤其是在内存使用方面。 此外,正如 Falmarri 所建议的,同时读取更多数据会大大提高性能。

也就是说,我不会期待奇迹,因为我不确定列表是存储所有数据量的最有效方式。 使用 NumPy 的数组及其对read/write binary files 的设施怎么样?在这个link 中有一节关于使用 numpyio.fread 读取原始二进制文件。我相信这应该正是您所需要的。

注意:就我个人而言,我从未使用过 NumPy;然而,它的主要存在理由正是处理大量数据 - 这就是您在问题中所做的。

【讨论】:

我会研究 NumPy,但是一旦它被加载,我该如何解析它呢?还是循环解析它?谢谢~ 在上面提供的链接中有一个关于使用 fread 读取二进制文件的部分。我将更新原始答案,以更好地说明这一点。 谢谢——那个链接很有帮助。【参考方案3】:

您一次读取和解压 2 ​​个字节

values[i] = struct.unpack(endian,f.read(bytes))[0]

您为什么不一次读取 1024 个字节?

【讨论】:

如果我这样做,它怎么知道它是一个 16 位整数而不是 32 或其他什么? ...它给了我一个错误:TypeError: Struct() argument 1 must be string, not int 我不认为 struct.unpack 是这里最好的解决方案。这意味着接收字符串,而不是二进制文件。【参考方案4】:

我也遇到过同样的问题,尽管在我的特殊情况下,我不得不转换一个非常奇怪的二进制格式 (500 MB) 文件,其中包含 166 个元素的交错块,这些元素是 3 字节有符号整数;所以我也遇到了从 24 位转换为 32 位有符号整数的问题,这会减慢速度。

我已经使用 NumPy 的 memmap(这只是使用 Python 的 memmap 的一种便捷方式)和 struct.unpack 对大块文件进行了解决。

使用此解决方案,我可以在大约 90 秒内(使用 time.clock() 计时)转换(读取、执行操作和写入磁盘)整个文件。

我可以上传部分代码。

【讨论】:

【参考方案5】:

我认为您在这里遇到的瓶颈是双重的。

根据您的操作系统和磁盘控制器,对f.read(2)f 是一个大文件的调用通常会被有效缓冲——usually。换句话说,操作系统会将磁盘上的一个或两个扇区(磁盘扇区通常为几 KB)读取到内存中,因为这并不比从该文件中读取 2 个字节贵很多。额外的字节被有效地缓存在内存中,为下一次调用读取该文件做好准备。不要依赖这种行为——这可能是你的瓶颈——但我认为这里还有其他问题。

我更关心将单字节转换为对 numpy 的短而单次调用。这些根本没有缓存。您可以将所有短裤保存在一个 Python 整数列表中,并在需要时(如果需要)将整个列表转换为 numpy。您还可以拨打一个电话 struct.unpack_from 来转换缓冲区中的所有内容,而不是一次转换一个短内容。

考虑:

#!/usr/bin/python

import random
import os
import struct
import numpy
import ctypes

def read_wopper(filename,bytes=2,endian='>h'):
    buf_size=1024*2
    buf=ctypes.create_string_buffer(buf_size)
    new_buf=[]

    with open(filename,'rb') as f:
        while True:
            st=f.read(buf_size)
            l=len(st)
            if l==0: 
                break
            fmt=endian[0]+str(l/bytes)+endian[1]    
            new_buf+=(struct.unpack_from(fmt,st))

    na=numpy.array(new_buf)        
    return na

fn='bigintfile'

def createmyfile(filename):
    bytes=165924350
    endian='>h'
    f=open(filename,"wb")
    count=0

    try: 
        for int in range(0,bytes/2):
            # The first 32,767 values are [0,1,2..0x7FFF] 
            # to allow testing the read values with new_buf[value<0x7FFF]
            value=count if count<0x7FFF else random.randint(-32767,32767)
            count+=1
            f.write(struct.pack(endian,value&0x7FFF))

    except IOError:
        print "file error"

    finally:
        f.close()

if not os.path.exists(fn):
    print "creating file, don't count this..."
    createmyfile(fn)
else:    
    read_wopper(fn)
    print "Done!"

我创建了一个 165,924,350 字节 (158.24 MB) 的随机短裤签名整数文件,相当于 82,962,175 个带符号的 2 字节短裤。使用这个文件,我运行了上面的read_wopper 函数,它运行在:

real        0m15.846s
user        0m12.416s
sys         0m3.426s

如果你不需要短裤是 numpy,这个函数在 6 秒内运行。所有这些都在 OS X、python 2.6.1 64 位、2.93 GHz Core i7、8 GB 内存上。如果将read_wopper 中的buf_size=1024*2 更改为buf_size=2**16,则运行时间为:

real        0m10.810s
user        0m10.156s
sys         0m0.651s

所以我认为你的主要瓶颈是单字节调用解包 - 而不是你从磁盘读取的 2 字节。您可能需要确保您的数据文件没有碎片,如果您使用的是 OS X,您的free disc space(和here)没有碎片。

编辑我发布了完整的代码来创建然后读取整数的二进制文件。在我的 iMac 上,读取随机整数文件的时间始终小于 15 秒。由于一次创作是短暂的,因此创作大约需要 1:23。

【讨论】:

谢谢——明天会试试这个,虽然目前正在使用 numpy.filefrom——但这对于没有安装 numpy 的机器来说可能很棒(这对于我管理的所有不同的机器来说都不是微不足道的)! 嗯...仍然是 2 分 50 秒(OS X,Python 2.6 64 位,4GB 内存)...感谢您对缓存的见解~ @Stephen:你的光盘有什么不寻常的地方吗?光盘格式是 NTFS 还是真的满了还是零散了?如果是 NTFS,则 OS X NTFS 驱动程序并不快。我将发布我的完整代码,并在相对空的 HFS 驱动器上尝试... 奇怪,它是 OS X Extended (Journaled) 但是 bash-3.2$ time python test.py 创建文件,不要计算这个...真正的 2m28.376s 用户 2m6.882s sys 0m3.664s bash-3.2$ time python test.py 完成!真实 0m28.485s 用户 0m23.273s 系统 0m1.509s 哎呀,格式不太好——无论如何,写的时间更长,但阅读的速度更快。我想知道二进制文件是否有不同之处。 Unix 头似乎在 bigintfile 上冻结(?),而在我的其他文件上却没有。感谢您的所有意见...

以上是关于将整个二进制文件读入 Python的主要内容,如果未能解决你的问题,请参考以下文章

Python 随机访问文件

如何在 C++ 中将整个文件读入 std::string?

将二进制文件读入结构(翻译指令)

在python中如何从二进制文件中读取信息

读取二进制文件碎片并转换为具有内存效率的整数

将二进制文件缓冲区的块读入不同的类型