如何在Python中写入位长不是8倍数的二进制文件?
How to write binary file with bit length not a multiple of 8 in Python?
我正在开发一个为项目生成虚拟二进制文件的工具。我们有一个描述真实二进制文件的规范,这些文件是从具有不同位长度的值流创建的。我使用输入和规范文件创建值列表,并使用 bitstring
库的 BitArray
class 转换值并将它们连接在一起。
问题是值的长度加起来并不总是完整的字节,我需要文件按原样包含这些位。通常我可以使用 BitArray.tofile()
,但该方法会自动在末尾用零填充文件。
还有其他方法可以将位写入文件吗?
显然原始二进制文件只能存储字节,即 8 位的倍数。
当您需要存储数据(位)和元数据(关于数据的数据,在本例中为位长度)时,有两种方式:
选择或发明一种文件格式,例如具有长度和 hexdump 的 JSON 结构(不是 space 有效,只是一个例子):
{
"length": 11,
"data": "c3a"
}
使用 sidecar file 文件作为元数据。这种情况不太常见。
正如大多数操作系统所见,文件包含字节流,有时也称为字符。在某些系统中,文本和二进制数据存储之间存在差异(例如 PDP-1 使用 6 位字符和 18 位字),但文件的大小以这些字节计算。对于某些系统,甚至不存储该级别,而是使用 end-of-file 字符来标记数据在最后一个块(无论是扇区、簇还是范围)中结束的位置。
您需要复制其中一种方法来存储多个位,例如使用 1-then-0s padding。该填充方法的缺点是您需要找到结尾才能知道是否是一串 0(和前面的 1)形成填充,而不是数据。
另一种方法可能是先存储位数,或者只存储每个写入块的位数。这样做需要一种编码,以便您知道大小字段的大小,例如一个字节,这意味着不超过 256 位的块。使用这种长度前缀方法,例如在 Pascal strings。
您可能还想考虑一种存储位序列的既定文件格式,例如 serial vector format。其中大多数效率不高,并且专为特定任务而设计(在这种情况下,存储电路模拟的时间序列)。
诸如此类的方案也可以推广到数据存储格式本身。示例包括 length-prefixed strings, UTF-8 code points, BitTorrent Bencoding or Exponential-Golomb coding。最后一个今天很重要,因为它允许任意大小并且受位串模块支持。
位串中一个相当简单的方法可能是向文件添加一个(对齐的)尾随字节,表示倒数第二个字节中有多少位被填充:
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-then-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:]
您需要填充,比如 7 位值,以便它匹配整个字节数:
1010101
(7 位)--> 01010101
1111
(4 位)--> 00001111
最高有效数字的填充不影响从文件中获取的数据。
我正在开发一个为项目生成虚拟二进制文件的工具。我们有一个描述真实二进制文件的规范,这些文件是从具有不同位长度的值流创建的。我使用输入和规范文件创建值列表,并使用 bitstring
库的 BitArray
class 转换值并将它们连接在一起。
问题是值的长度加起来并不总是完整的字节,我需要文件按原样包含这些位。通常我可以使用 BitArray.tofile()
,但该方法会自动在末尾用零填充文件。
还有其他方法可以将位写入文件吗?
显然原始二进制文件只能存储字节,即 8 位的倍数。
当您需要存储数据(位)和元数据(关于数据的数据,在本例中为位长度)时,有两种方式:
选择或发明一种文件格式,例如具有长度和 hexdump 的 JSON 结构(不是 space 有效,只是一个例子):
{ "length": 11, "data": "c3a" }
使用 sidecar file 文件作为元数据。这种情况不太常见。
正如大多数操作系统所见,文件包含字节流,有时也称为字符。在某些系统中,文本和二进制数据存储之间存在差异(例如 PDP-1 使用 6 位字符和 18 位字),但文件的大小以这些字节计算。对于某些系统,甚至不存储该级别,而是使用 end-of-file 字符来标记数据在最后一个块(无论是扇区、簇还是范围)中结束的位置。
您需要复制其中一种方法来存储多个位,例如使用 1-then-0s padding。该填充方法的缺点是您需要找到结尾才能知道是否是一串 0(和前面的 1)形成填充,而不是数据。
另一种方法可能是先存储位数,或者只存储每个写入块的位数。这样做需要一种编码,以便您知道大小字段的大小,例如一个字节,这意味着不超过 256 位的块。使用这种长度前缀方法,例如在 Pascal strings。
您可能还想考虑一种存储位序列的既定文件格式,例如 serial vector format。其中大多数效率不高,并且专为特定任务而设计(在这种情况下,存储电路模拟的时间序列)。
诸如此类的方案也可以推广到数据存储格式本身。示例包括 length-prefixed strings, UTF-8 code points, BitTorrent Bencoding or Exponential-Golomb coding。最后一个今天很重要,因为它允许任意大小并且受位串模块支持。
位串中一个相当简单的方法可能是向文件添加一个(对齐的)尾随字节,表示倒数第二个字节中有多少位被填充:
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-then-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:]
您需要填充,比如 7 位值,以便它匹配整个字节数:
1010101
(7 位)--> 01010101
1111
(4 位)--> 00001111
最高有效数字的填充不影响从文件中获取的数据。