将“位”写入C ++文件流

11

如何每次向文件流或文件结构中写入'一个比特(bit)'?

是否可以写入队列并将其刷新到文件中?

在C#或Java中是否可行?

在尝试实现哈夫曼编码的实例时需要这样做。我无法直接向文件中写入比特,因此将它们写入一个位集(bitset)中,然后(当压缩完成时)每次写入其中的8比特块(除了最后一个)。


你错过了任何一种语言吗?大多数语言不允许一次写入少于一个字节的内容。但你可以测试单个位并打印结果。 - dirkgently
7个回答

13

缓冲每个位直到累积了整个字节似乎是一个好主意:

byte b;
int s;

void WriteBit(bool x)
{
    b |= (x ? 1 : 0) << s;
    s++;

    if (s == 8)
    {
        WriteByte(b);
        b = 0;
        s = 0;
    }
}

当要写入的比特数不是8的倍数时,您只需要处理这种情况。


看起来不错。最后一个情况可以使用 bool flush 参数和 if (s == 8 || flush) 测试来处理。 - Martin Wickman
请确保 s 被初始化为 0。 - Mark B
1
请注意,在 C 标准中,并没有定义或暗示字节内的“第一个”或“最后一个”位,只有最重要或最不重要的位,可能与移位相关的“左侧”和“右侧”。因此,WriteBit 将不得不自行决定(并记录)位应该首先写入最重要还是最不重要。您选择了最不重要的方式,这很公平,并且维基百科声称它是串行通信硬件级别中迄今为止最常见的方式。我从未深入了解过串行驱动程序。 - Steve Jessop

8
你可以使用 boost::dynamic_bitsetstd::ostream_iterator 以简洁的方式实现所需的结果:
#include <fstream>
#include <iterator>
#include <boost/dynamic_bitset.hpp>

typedef boost::dynamic_bitset<unsigned char> Bitset;

// To help populate the bitset with literals */
Bitset& operator<<(Bitset& lhs, bool val) {lhs.push_back(val); return lhs;}

int main()
{
    Bitset bitset;
    bitset<<0<<1<<0<<1<<0<<1<<0<<1
          <<1<<0<<1<<0;

    std::ofstream os("data.dat", std::ios::binary);
    std::ostream_iterator<char> osit(os);
    boost::to_block_range(bitset, osit);

    return 0;
}

我通过指定模板参数为unsigned char,将我的dynamic_bitset块大小设置为8位。您可以通过指定更大的整数类型来增加块大小。 boost::to_block_range将位集按块转储到给定的输出迭代器中。如果在最后一个块中存在空余的剩余位,则会用零填充它们。
当我在十六进制编辑器中打开data.dat时,我看到:AA 05。这是在小端平台(x64)上的结果。

3
您正在使用哪个文件系统?
最有可能的是,它以字节形式存储文件的长度(有没有不以字节为单位的文件系统?)。因此,不可能有一个物理文件的大小不是整数个字节。
因此,如果您正在将该文件写入作为位流,则必须在完成时截断最后几位,或者写出具有剩余位中所含的垃圾的最终字节。
以下是一些Python代码供您参考。
class BitFile(file):
    def __init__(self, filename, mode):
        super(BitFile, self).__init__(filename, mode)
        self.bitCount=0
        self.byte = 0

    def write(self, bit):
        self.bitCount+=1
        self.byte = self.byte*2+bit
        if self.bitCount%8==0:
            super(BitFile, self).write(chr(self.byte))
            self.byte=0

    def close(self):
        if self.bitCount%8!=0:
            super(BitFile, self).write(chr(self.byte))
        super(BitFile, self).close()     

with BitFile("bitfile.bin","w") as bf:
    bf.write(1)
    bf.write(1)
    bf.write(1)
    bf.write(0)
    bf.write(0)
    bf.write(0)
    bf.write(0)
    bf.write(0)
    bf.write(1)

0

你实际上做不到。我相信问题不在于语言或文件系统,而是硬件问题。处理器设计用于处理字节。你能做的最接近的方法可能是反复写入最后一个字节,右侧填充零,逐个更改它们。

例如,要写入位'11011',可以执行以下操作(Python示例,但任何语言都应该有这样的功能:

f.write(chr(0b10000000))
f.flush()
f.seek(-1)
f.write(chr(0b11000000))
f.flush()
f.seek(-1)
f.write(chr(0b11000000))
f.flush()
f.seek(-1)
f.write(chr(0b11010000))
f.flush()
f.seek(-1)
f.write(chr(0b11011000)) 
f.flush()

你并不希望从这里获得某种性能提升,是吗?

FYI,C和C++语言没有声明二进制常量的功能。 - Thomas Matthews

0
我建议分配一个相当大的缓冲区(至少4096字节),并在其填满时将其刷新到磁盘上。使用一个字节的缓冲区通常会导致性能不佳。

如果我想压缩一个巨大的文件,比如OTF阿拉伯字形位置数据。它的大小为48MB,在压缩后为29MB。所以你的方法不是理论上的,并且会浪费内存。 - sorush-r
1
你误解了我的方法。我只是建议使用比一个字节更大的缓冲区,以使刷新频率更低,而不是为了适应你的全部数据而使用缓冲区。 - Tronic

0

我曾经为哈夫曼解码做过这件事,最终将位写成字符,并将所有内容作为普通的C字符串在内部处理。

这样你就不必担心尾随字节,而且它也更易于人类阅读。此外,检查位更容易,因为只需要访问字符数组(binbuf[123] == '1'),而不必摆弄位。虽然不是最优化的解决方案,但它很好地解决了我的问题。

显而易见的缺点是这种表示使用更多的内存。


0

这里的问题在于许多平台没有直接的位访问。它们将位分组成最小的包,通常是字节或字。此外,流设备的协议不便于传输单个位。

处理单个位的常见方法是将它们打包到最小的可移植和(可寻址)可访问单元中。未使用的位通常设置为零。这可以通过二进制算术运算(OR、AND、EXCLUSIVE-OR、NOT等)来实现。

随着现代处理器的出现,位操作会减慢机器的性能。内存很便宜,有了大的寻址空间,位打包的理由变得更加困难。一般来说,位打包是保留给硬件操作(以及传输协议)的。例如,如果处理器的字容量为16位,则处理器可能比在一个字中进行16位操作更快地处理16个字。

此外,请记住,写入和从内存读取通常比从流进行I/O更快。高效的系统在传输数据之前将数据缓冲到内存中。您可能需要考虑在设计中使用此技术。减少I/O操作将提高程序的性能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接