C++ Huffman压缩文件异常求助:压缩后体积未减小
早上好!我来帮你解决Huffman压缩后体积反而变大的问题,还有你那出问题的二进制读写代码~
问题根源拆解
首先得搞懂为什么你的压缩体积反而更大:
- 你现在是把Huffman生成的每个二进制位(0/1)当成一个ASCII字符存储,每个字符占1字节(8位)——等于把原始编码直接放大了8倍!这肯定比原文件大啊。Huffman压缩的核心是把多个二进制位打包成一个字节,而不是逐个存成字符。
- 你的二进制读写代码有严重逻辑错误:
- 写入时,你用
file.write((char*)&stringa,len);,这里stringa本身就是char指针,取它的地址传给write,等于写的是指针的内存地址值,而不是字符串内容! - 读取时同样犯了指针地址的错误,而且用
string stringa = buff;会因为二进制内容里可能存在的\0截断数据。
- 写入时,你用
正确实现方案
要实现真正的Huffman压缩,需要分三步:把编码位打包成字节、修正二进制读写、还要存储Huffman映射表(否则没法解压)。
1. 把编码字符串打包成字节流
先写一个工具函数,把0/1字符串每8位打包成一个字节,不足8位的末尾补0,同时记录补位数量(解压时要去掉这些补位):
#include <vector> #include <utility> #include <string> #include <iostream> // 把二进制位字符串转成字节数组,返回(字节数组,补位数量) std::pair<std::vector<unsigned char>, int> EncodeBitsToBytes(const std::string& bitString) { std::vector<unsigned char> bytes; int paddingBits = 0; int totalBits = bitString.size(); // 计算需要补多少位到8的倍数 if (totalBits % 8 != 0) { paddingBits = 8 - (totalBits % 8); } int paddedTotalBits = totalBits + paddingBits; // 每8位转成一个字节 for (int i = 0; i < paddedTotalBits; i += 8) { std::string byteStr = bitString.substr(i, 8); // 如果是最后一段,补0到8位 if (byteStr.size() < 8) { byteStr += std::string(8 - byteStr.size(), '0'); } unsigned char byte = 0; for (char c : byteStr) { byte <<= 1; if (c == '1') { byte |= 1; } } bytes.push_back(byte); } return {bytes, paddingBits}; }
2. 修正二进制读写函数
现在修改你的读写函数,要把补位数量也写入文件(解压时需要),并且正确操作字节数据:
#include <fstream> #include <string> #include <cerrno> class IOFile { public: void WriteBinary(const std::string& bitStr, const std::string& nome); std::string ReadBinary(const std::string& percorso); }; void IOFile::WriteBinary(const std::string& bitStr, const std::string& nome) { auto [bytes, padding] = EncodeBitsToBytes(bitStr); std::ofstream file(nome.c_str(), std::ios::out | std::ios::binary); if (!file.is_open()) { std::cerr << "无法打开文件: " << nome << ",错误码: " << errno << std::endl; return; } // 先写入补位数量(1字节足够,最多补7位) file.write(reinterpret_cast<const char*>(&padding), sizeof(padding)); // 写入打包后的字节流 file.write(reinterpret_cast<const char*>(bytes.data()), bytes.size()); file.close(); } std::string IOFile::ReadBinary(const std::string& percorso) { std::ifstream file(percorso.c_str(), std::ios::in | std::ios::binary); if (!file.is_open()) { std::cerr << "无法打开文件: " << percorso << ",错误码: " << errno << std::endl; return ""; } // 读取补位数量 int paddingBits = 0; file.read(reinterpret_cast<char*>(&paddingBits), sizeof(paddingBits)); // 读取剩余的字节数据 file.seekg(0, std::ios::end); std::streampos totalFileSize = file.tellg(); std::streampos bytesSize = totalFileSize - sizeof(paddingBits); file.seekg(sizeof(paddingBits)); std::vector<unsigned char> bytes(bytesSize); file.read(reinterpret_cast<char*>(bytes.data()), bytesSize); file.close(); // 把字节转回二进制位字符串 std::string bitStr; for (unsigned char byte : bytes) { // 从高位到低位解析每个位 for (int i = 7; i >= 0; --i) { bitStr += ((byte >> i) & 1) ? '1' : '0'; } } // 去掉末尾补的0 if (paddingBits > 0 && bitStr.size() >= paddingBits) { bitStr = bitStr.substr(0, bitStr.size() - paddingBits); } return bitStr; }
3. 别忘了存储Huffman映射表
现在你只存了编码后的位字符串,但解压时需要知道每个编码对应哪个字符!所以压缩文件还需要包含字符到编码的映射关系(或者序列化的Huffman树)。比如可以在文件开头先写入字符集的大小,再逐个写入字符、编码长度、编码内容,这样解压时就能重建映射表。
验证效果
现在用你提供的编码字符串测试:原编码字符串长度是200多位,打包成字节后大概是25-30字节,加上补位和映射表的开销,肯定比原字符串(原意大利语句子大概40多字节)小,这样就能实现真正的压缩了。
内容的提问来源于stack exchange,提问作者Matta
相关产品推荐
相关产品推荐

