You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:18:15