C++实现霍夫曼编码时如何将编码转为比特写入压缩文件
霍夫曼编码比特流压缩写入实现
你已经完成了霍夫曼编码映射表的构建,接下来要把字符串格式的0/1编码转成实际比特存储,核心是用单字节做缓冲,攒够8位再写入文件,避免把'0'/'1'当1字节字符存储浪费空间,具体实现如下。
核心逻辑
- 用1个
unsigned char类型变量作为8位比特缓冲区,搭配1个计数器记录当前缓冲区已经填充了多少个有效比特,初始值都为0。 - 逐字符遍历原文本,从编码映射表中取出对应霍夫曼编码串,逐位处理每个0/1:将缓冲区左移1位腾出空位,当前位是1就把缓冲区最低位置1,计数器累加。
- 每当计数器累加到8,说明缓冲区已经攒够1个完整字节,直接把这个字节写入输出文件,随后重置缓冲区和计数器。
- 所有编码处理完成后,如果缓冲区还有不足8位的有效比特,低位补0凑成1个完整字节写入文件,必须记录补0的个数,否则解压时会把补的0当成有效编码解析出错。
- 压缩文件必须在开头存储编码表、补0个数这些元信息,否则后续无法正确解压。
适配现有代码的修改
1. 新增类成员变量
在Huffman类的私有成员区添加比特写入相关的缓冲变量:
unsigned char bit_buf = 0; // 单字节比特缓冲区 int bit_cnt = 0; // 缓冲区已填充的有效比特数 int padding = 0; // 最后一个字节的补0数量
2. 替换原空实现的CompressedFile函数
把你原来的CompressedFile函数替换为以下实现,注意打开文件必须用二进制模式,避免文本模式自动转义特殊字符损坏比特流:
void CompressedFile() { // 用二进制模式打开输出文件,自定义后缀避免被当作文本文件误打开 ofstream compressed("CompressedFile.huf", ios::out | ios::binary); if (!compressed.is_open()) { cerr << "压缩文件创建失败!" << endl; return; } // 写入文件头:解压必需的元信息 // 先写编码表条目数 int map_size = um.size(); compressed.write(reinterpret_cast<char*>(&map_size), sizeof(map_size)); // 逐个写入编码表项:字符 + 编码长度 + 编码串 for (auto& item : um) { char ch = item.first; string code = item.second; int code_len = code.size(); compressed.write(&ch, sizeof(ch)); compressed.write(reinterpret_cast<char*>(&code_len), sizeof(code_len)); compressed.write(code.c_str(), code_len); } // 预留位置存储最后补0的数量,等比特写完再回填 streampos padding_pos = compressed.tellp(); compressed.write(reinterpret_cast<char*>(&padding), sizeof(padding)); // 逐字符转比特写入 bit_buf = 0; bit_cnt = 0; for (char ch : text) { string& code = um[ch]; for (char bit : code) { bit_buf <<= 1; // 左移1位腾出最低位存新比特 if (bit == '1') { bit_buf |= 1; // 当前位为1则置最低位为1 } bit_cnt++; // 攒够8位写1字节到文件 if (bit_cnt == 8) { compressed.write(reinterpret_cast<char*>(&bit_buf), 1); bit_buf = 0; bit_cnt = 0; } } } // 处理最后不足8位的剩余比特 padding = 0; if (bit_cnt > 0) { padding = 8 - bit_cnt; bit_buf <<= padding; // 低位补0凑整字节 compressed.write(reinterpret_cast<char*>(&bit_buf), 1); } // 回填补0数量到预留的文件头位置 compressed.seekp(padding_pos); compressed.write(reinterpret_cast<char*>(&padding), sizeof(padding)); compressed.close(); cout << "压缩完成,输出文件为 CompressedFile.huf" << endl; }
3. 在主函数中调用压缩逻辑
在main函数里obj.HuffmanTree();的后面加一行调用即可:
obj.CompressedFile();
额外优化建议
- 你原来的字符频率统计是O(n²)复杂度,大文件下速度很慢,可以替换为O(n)的数组计数实现:
void CharacterFrequency() { vector<int> freq(256, 0); for (unsigned char ch : text) { freq[ch]++; } for (int i = 0; i < 256; i++) { if (freq[i] > 0) { PriorityQueue(static_cast<char>(i), freq[i]); } } }
- 记得写完代码后加上霍夫曼树节点的内存释放逻辑,不然会有内存泄漏,新手阶段可以暂时忽略,功能跑通后再补就行。
- 解压的时候逻辑反过来:先读文件头的编码表和补0数,再逐字节读文件,把每个字节拆成8个比特,拼起来匹配编码表还原原字符,最后跳过末尾补的padding个0即可。
内容的提问来源于stack exchange,提问作者Muhammad Taimoor
相关产品推荐
相关产品推荐

