背景

大二上期末专周选了一道手撸哈夫曼树的题目,其中有一个非必实现功能点:将哈夫曼编码结果存储起来,方便下次直接读取使用。

当时设计的哈夫曼树结构体是这样的

// 字符-权重-编码
typedef struct Code
{
  // 代表的字符
  char character;
  // 权重
  int weight;
  // 编码
  string decode;
  // 判断是否被匹配
  bool isMatched;
} Code;

看下文件读写部分的代码

void writeToFile(Code arr[], int size) {
    ofstream codeBook;
    codeBook.open("hfmTree.dat", ios::out | ios::binary);
    for (int i = 0; i < size; i++) {
        codeBook.write(reinterpret_cast<const char *>(arr + i), sizeof(Code));
    }
    codeBook.close();
    ofstream hfmTreeSize;
    hfmTreeSize.open("hfmTreeSize.dat");
    hfmTreeSize << size;
    hfmTreeSize.close();
}

void readFromFile(Code arr[], int size) {
    ifstream codeBook;
    codeBook.open("hfmTree.dat", ios::in | ios::binary);
    for (int i = 0; i < size; i++) {
        codeBook.read(reinterpret_cast<char *>(arr + i), sizeof(Code));
    }
    codeBook.close();
}

写之前

读之后

可以发现,数据乱了......

原因

在C++中,std::string 实际上是一个动态分配内存的字符数组。

程序会根据'\0'判断一个完整的字符串,看下以下这段程序

char arr[] = {'A', 'B', 0x00, 'C'};
string str(arr);
string str1(arr, sizeof(arr));
cout << str << endl;
cout << str1 << endl;

运行结果

所以大概可以猜测,就是std::string 导致的读写前后数据错乱。

解决方案

手动设置每个字段该写入多少个字节,并且同样通过指定的长度读取

// 二进制文件格式:
// 1B character , 4B weight , 1B isMatched ,
// 4B decodeLength , ($decodeLength)B decode
void writeToFile(Code arr[], int size) {
    ofstream codeBook;
    codeBook.open("hfmTree.dat", ios::out | ios::binary);
    for (int i = 0; i < size; i++) {
        Code code = arr[i];
        // 写入 character
        codeBook.write(&code.character, sizeof(char));
        // 写入 weight
        codeBook.write(reinterpret_cast<char *>(&code.weight), sizeof(int));
        // 写入 isMatched
        codeBook.write(reinterpret_cast<char *>(&code.isMatched), sizeof(bool));
        // 写入decode
        // 获取decode长度
        int decodeLength = code.decode.length();
        // 写入长度
        codeBook.write(reinterpret_cast<char *>(&decodeLength), sizeof(int));
        // 写入decode
        codeBook.write(code.decode.c_str(), decodeLength);
    }
    codeBook.close();
    ofstream hfmTreeSize;
    hfmTreeSize.open("hfmTreeSize.dat");
    hfmTreeSize << size;
    hfmTreeSize.close();
}

void readFromFile(Code arr[], int size) {
    ifstream codeBook;
    codeBook.open("hfmTree.dat", ios::in | ios::binary);
    for (int i = 0; i < size; i++) {
        Code *code = arr + i;
        // 读取 character
        codeBook.read(&code->character, sizeof(char));
        // 读取 weight
        codeBook.read(reinterpret_cast<char *>(&code->weight), sizeof(int));
        // 获取 isMatched
        codeBook.read(reinterpret_cast<char *>(&code->isMatched), sizeof(bool));
        // 获取长度
        int decodeLength = 0;
        codeBook.read(reinterpret_cast<char *>(&decodeLength), sizeof(int));
        // 重新分配decode大小
        code->decode.resize(decodeLength);
        // 写入decode
        codeBook.read(&code->decode[0], decodeLength);
    }
    codeBook.close();
}

读取结果

我是三叶的狗,QQ2279538834