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

我需要从 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 字节)。有没有更好的办法?

【问题讨论】:

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

标签: python numpy


【解决方案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 上冻结(?),而在我的其他文件上却没有。感谢您的所有意见...
    猜你喜欢
    • 1970-01-01
    • 2016-03-30
    • 2015-01-30
    • 2013-06-22
    • 1970-01-01
    • 2014-01-07
    • 2011-05-14
    • 2017-12-01
    • 1970-01-01
    相关资源
    最近更新 更多