将整个二进制文件读入Python

时间:2010-12-12 19:47:05

标签: python numpy

我需要从Python导入一个二进制文件 - 内容是16位整数的标记,大端。

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

我想创建一个像:

这样的函数
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字节)。还有更好的方法吗?

5 个答案:

答案 0 :(得分:10)

使用numpy.fromfile

答案 1 :(得分:4)

我会直接阅读直到EOF(这意味着检查接收一个空字符串),然后删除然后需要使用range()和getsize。
或者,使用xrange(而不是range)可以改善一些事情,尤其是在内存使用方面 此外,正如Falmarri所建议的那样,同时阅读更多数据会大大提高性能。

那就是说,我不会指望奇迹,也因为我不确定列表是存储所有数据量的最有效方式。
如何使用NumPy的数组及其设施read/write binary files?在这个link中,有一节介绍如何使用numpyio.fread读取原始二进制文件。我相信这应该是你所需要的。

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

答案 2 :(得分:2)

您正在一次读取和解包2个字节

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

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

答案 3 :(得分:2)

我遇到了同样的问题,虽然在我的特殊情况下,我不得不转换一个非常奇怪的二进制格式(500 MB)文件,其中包含166个元素的隔行扫描块,这些块是3字节有符号整数;所以我也遇到了从24位转换为32位有符号整数的问题,这会让事情变得缓慢。

我已经使用NumPy的memmap(它只是使用Python的memmap的一种方便方法)和struct.unpack在文件的大块上解决了它。

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

我可以上传部分代码。

答案 4 :(得分:1)

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

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

我更关注单字节转换为短暂和单次调用numpy。这些都没有缓存。您可以将所有短路保存在Python的int列表中,并在需要时(如果需要)将整个列表转换为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内存。如果您将buf_size=1024*2中的read_wopper更改为buf_size=2**16,则运行时间为:

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

因此,我认为你的主要瓶颈是单字节调用解包 - 而不是你从光盘读取的2字节。您可能希望确保数据文件不会碎片化,如果您使用OS X,则free disc space(和here)不会碎片化。

编辑我发布了完整的代码来创建然后读取整数的二进制文件。在我的iMac上,我一直得到&lt; 15秒读取随机整数文件。创建大约需要1:23,因为创建一次只有一个短。