【问题标题】:How to Write a binary file in c++如何在 C++ 中编写二进制文件
【发布时间】:2012-06-25 22:46:55
【问题描述】:

我正在尝试在 c++ 中实现霍夫曼编码算法。

我的问题是:在我得到每个字符的等效二进制字符串之后,我怎样才能将这些零和一写成二进制文件而不是字符串 0 或字符串 1?

提前谢谢...

【问题讨论】:

  • 只是为了说清楚。我所说的二进制等效字符串是什么意思:例如,如果 A 由 010 编码,我想在文件上写 010 作为二进制 0 和二进制 1,所以总数是 3 位而不是 24 位(3 字节),每个 ASCII 等效字符 0 和字符 1 的二进制。
  • 我们不明白的是你有什么,而不是你最终需要什么。
  • 我有一个包含 3 个字段的数据结构(用户定义);字符、频率和等效位编码。现在,首先我要从文本文件中读取文本并填充每个字符的频率字段,将数据结构转换为二叉树,然后遍历树以找到每个字符的等效位编码。最后(这是我的问题):我想通过从原始文本中读取每个字符并将其等效位字符串(使用二叉树)写入二进制文件来生成原始文本文件的压缩版本。
  • 那么您可以使用我在下面包含的bitstream 类使用push_bit(bool) 写入您的编码位,然后通过写入@ 返回的数组的(size() + 7) / 8 字节将其写入文件987654324@.

标签: c++ encoding binary huffman-code


【解决方案1】:

希望这段代码可以帮到你。

  • 您从表示输入文件每个字符的连续编码的字节序列(1 和 0)开始。
  • 您获取序列的每个字节并在临时字节中添加一个位 (char byte)
  • 每次填充一个字节时,都会将其写入文件(为了提高效率,您也可以等待获得更大的数据)
  • 最后,您将剩余的位写入文件,例如用尾随零填充
  • 正如 akappa 正确指出的那样,如果在每次文件写入操作之后将 byte 设置为 0,则可以删除 else 分支(或者,更一般地说,每次它已被完全填充并刷新到其他地方) ,所以只需要写1s

void writeBinary(char *huffmanEncoding, int sequenceLength)
{
    char byte = 0;
    // For each bit of the sequence
    for (int i = 0; i  < sequenceLength; i++) {
        char bit = huffmanEncoding[i];

        // Add a single bit to byte
        if (bit == 1) {
            // MSB of the sequence to msb of the file
            byte |= (1 << (7 - (i % 8)));
            // equivalent form: byte |= (1 << (-(i + 1) % 8);
        }
        else {
            // MSB of the sequence to msb of the file
            byte &= ~(1 << (7 - (i % 8)));
            // equivalent form: byte &= ~(1 << (-(i + 1) % 8);
        }

        if ((i % 8) == 0 && i > 0) {
            //writeByteToFile(byte);
        }
    }

    // Fill the last incomplete byte, if any, and write to file
}

【讨论】:

  • 所以您假设每个编码都存储有一个代表每个位的字节?听起来像是对内存的巨大浪费。顺便说一句,您以错误的顺序输出位(应该是(-i) % 8),您可以通过在每次写入后的开头清除byte 来擦除else 分支。
  • @akappa 这就是 OP 作为输入的内容:“我得到了每个字符的等效二进制字符串”。另外,我不同意位排序问题,我将它们作为编码字符串输出。编码字符串的第一个字节,将是文件中的第一位。清除其他的好点。
  • 文件中的第一位将是字节的最高位,而您的第一位将是通过将所有位合并到一个正确的字节数组。
  • PS:我对他的“二进制字符串”的解释与你不同(每个字符的字节数组),但我同意他的措辞模棱两可。
  • @akappa,是的。但是,我更改了位的顺序,因为您完全正确。
【解决方案2】:

在不同的数据结构中单独获取每个字符的编码是一个失败的解决方案,因为您需要将生成的二进制文件中的每个字符的编码并列:单独存储它们使得这与直接将它们连续存储在一个位向量

这种考虑建议使用std::vector&lt;bool&gt; 来执行您的任务,但这是一个失败的解决方案,因为它不能被视为 c 样式数组,而您在输出时确实需要它。

这个question 准确地询问哪些是std::vector&lt;bool&gt; 的有效替代品,所以我认为这个问题的答案非常适合你的问题。

顺便说一句,我要做的就是将std::vector&lt;uint8_t&gt; 包装在一个适合您需要的类下,就像附加的代码一样:

#include <iostream>
#include <vector>
#include <cstdint>
#include <algorithm>
class bitstream {
private:
    std::vector<std::uint8_t> storage;
    unsigned int bits_used:3;
    void alloc_space();
public:
    bitstream() : bits_used(0) { }

    void push_bit(bool bit);

    template <typename T>
    void push(T t);

    std::uint8_t *get_array();

    size_t size() const;

    // beware: no reference!
    bool operator[](size_t pos) const;
};

void bitstream::alloc_space()
{
    if (bits_used == 0) {
        std::uint8_t push = 0;
        storage.push_back(push);
    }
}

void bitstream::push_bit(bool bit)
{
    alloc_space();
    storage.back() |= bit << 7 - bits_used++;
}

template <typename T>
void bitstream::push(T t)
{
    std::uint8_t *t_byte = reinterpret_cast<std::uint8_t*>(&t);
    for (size_t i = 0; i < sizeof(t); i++) {
        uint8_t byte = t_byte[i];
        if (bits_used > 0) {
            storage.back() |= byte >> bits_used;
            std::uint8_t to_push = (byte & ((1 << (8 - bits_used)) - 1)) << bits_used;
            storage.push_back(to_push);
        } else {
            storage.push_back(byte);
        }
    }
}

std::uint8_t *bitstream::get_array()
{
    return &storage.front();
}

size_t bitstream::size() const
{
    const unsigned int m = 0;
    return std::max(m, (storage.size() - 1) * 8 + bits_used);
}

bool bitstream::operator[](size_t size) const
{
    // No range checking
    return static_cast<bool>((storage[size / 8] >> 7 - (size % 8)) & 0x1);
}

int main(int argc, char **argv)
{
    bitstream bs;
    bs.push_bit(true);
    std::cout << bs[0] << std::endl;
    bs.push_bit(false);
    std::cout << bs[0] << "," << bs[1] << std::endl;
    bs.push_bit(true);
    bs.push_bit(true);
    std::uint8_t to_push = 0xF0;
    bs.push_byte(to_push);
    for (size_t i = 0; i < bs.size(); i++)
        std::cout << bs[i] << ",";
    std::cout << std::endl;
}

【讨论】:

    【解决方案3】:

    您不能写入只有位的二进制文件;写入的最小数据大小为 1 个字节(因此为 8 位)。

    所以你应该做的是创建一个缓冲区(任何大小)。

    char BitBuffer;
    

    写入缓冲区:

    int Location;
    bool Value;
    
    if (Value)
        BitBuffer |= (1 << Location);
    else
        BitBuffer &= ~(1 << Location)
    

    代码(1 &lt;&lt; Location) 生成一个除Location 指定的位置外全为0 的数字。然后,如果Value 设置为true,则将Buffer 中的相应位设置为1,否则设置为0。使用的二进制操作相当简单,如果你不理解它们,它应该在任何好的 C++ 书籍/教程中。

    位置应该是 范围内的数字,因此在这种情况下为 。

    使用 fstream 时,将缓冲区写入文件相对简单。请记住以二进制形式打开它。

    ofstream File;
    File.open("file.txt", ios::out | ios::binary);
    File.write(BitBuffer, sizeof(char))
    

    编辑:注意到一个错误并修复它。

    EDIT2:你不能在二进制模式下使用&lt;&lt; 运算符,我忘了。

    替代解决方案:使用std::vector&lt;bool&gt;std::bitset 作为缓冲区。

    这应该更简单,但我想我可以帮助你更多一点。

    void WriteData (std::vector<bool> const& data, std::ofstream& str)
    {
        char Buffer;
        for (unsigned int i = 0; i < data.size(); ++i)
        {
           if (i % 8 == 0 && i != 0)
               str.write(Buffer, 1);
           else
               // Paste buffer setting code here
               // Location = i/8;
               // Value = data[i];
        }
        // It might happen that data.size() % 8 != 0. You should fill the buffer
        // with trailing zeros and write it individually.
    }
    

    【讨论】:

    • 谢谢先生,但如果您不介意的话,我有几个问题。首先这行代码做什么?诠释位置;位缓冲区 &= 位缓冲区 && (1
    • 简单的答案:(1) 一个 char 的缓冲区是无用的 (2) std::vector 完全损坏了,并且 (3) std::bitset 不是可消耗的。
    • @akappa 我的印象是std::vector&lt;bool&gt; 在使用它自己的[] 运算符索引时可以工作,所以上面的例子应该可以工作,即使它远非最佳。另外,我明确指出缓冲区可以比char 更大——所以无论如何OP 都可能以C 样式数组结束。反对票对我来说似乎很不公平。
    • 您说过:它远非最佳。除非您想要一个玩具实现,否则您应该直接将编码写入易于输出的内容。有一些解决方案可以做到这一点(我为压缩机写了一个),所以我认为这不是一个好的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-12
    • 1970-01-01
    • 2015-11-29
    • 1970-01-01
    • 2017-09-22
    • 2017-02-18
    相关资源
    最近更新 更多