如何在Python中写位长不为8的倍数的二进制文件?

时间:2019-05-12 11:06:47

标签: python binaryfiles binary-data bitstring

我正在使用一种工具为项目生成虚拟二进制文件。我们有一个描述真实二进制文件的规范,该二进制文件是由具有不同位长的值流创建的。我使用输入文件和规范文件来创建值列表,并使用bitstring库的BitArray类来转换值并将它们连接在一起。

问题在于值的长度并不总等于完整字节,我需要文件按原样包含位。通常我可以使用BitArray.tofile(),但是该方法会自动在文件末尾填充零。

还有另一种方法将位写入文件吗?

3 个答案:

答案 0 :(得分:3)

显然,原始二进制文件只能存储字节,即8位的倍数。

当您需要存储数据(位)和元数据(关于数据的数据,在这种情况下,长度为位)时,有两种方法:

  • 选择或发明一种文件格式,例如具有长度和十六进制转储的JSON结构(不节省空间,仅是示例):

    {
      "length": 11,
      "data": "c3a"
    }
    
  • 使用sidecar file文件存储元数据。这不太常见。

答案 1 :(得分:1)

在大多数操作系统中,文件都包含字节流,有时也称为字符。在某些系统中,文本存储和二进制数据存储之间存在差异(例如PDP-1使用6位字符和18位字),但是文件大小以这些字节为单位。对于某些系统,甚至不存储该级别,但文件末尾字符用于标记数据在最后一个块(扇区,簇或范围)中的结束位置。

您将需要复制这些方法之一来存储许多位,例如使用1-then-0s padding。该填充方法的缺点是您需要找到结尾以了解是否由0(和前一个1)组成的字符串构成了填充,而不是数据。

另一种方法可能是先存储位数,或者仅存储每个已写入块的位数。这样做需要一种编码,以便您知道size字段的大小,例如一个字节,这意味着不超过256位的块。例如,使用该长度前缀方法。在Pascal strings中。

您可能还想考虑存储位序列的已建立文件格式,例如serial vector format。其中大多数效率不是很高,并且是为特定任务而设计的(在这种情况下,是存储电路仿真的时间序列)。

诸如此类的方案也可以概括为数据存储格式本身。示例包括length-prefixed stringsUTF-8 code pointsBitTorrent BencodingExponential-Golomb coding。今天的最后一个是有意义的,因为它允许任意大小并且由bitstring模块支持。

位串中一种相当简单的方法可能是在文件中添加一个(对齐的)尾随字节,以表明倒数第二个字节中有多少位被填充:

def pad(data: bitstring.BitArray):
    padding = data.bytealign()
    data.append(bitstring.Bits(chr(padding)))
def unpad(data: bitstring.BitArray):
    padding = data[-8:].uint
    del data[-8-padding:]

如果您要逐个读取文件,则必须注意在到达最后两个字节时进行此填充。

这里是1到0的变体:

def pad(data: bitstring.BitArray):
    data.append(bitstring.Bits(length=1, uint=1))
    data.bytealign()
def unpad(data: bitstring.BitArray):
    last1 = data.rfind(bitstring.Bits(length=1, uint=1))[0]
    del data[last1:]

答案 2 :(得分:0)

您需要对7位值进行填充,使其与整个字节数匹配:

1010101(7位)-> 01010101

1111(4位)-> 00001111

最高有效数字的填充不会影响从文件中获取的数据。