Skip to content

压缩算法的调研

复制本地路径 | 在线编辑

项目中遇到了一个需求是对一些内容压缩放入 Flash 中,其中需要用到压缩算法,对其进行了调研。具体的各个常见算法对比,直接问 AI 就行了,能给你一个漂亮的表格。这里我直接说一下我的感受。

直接说结论就是 zstd 就挺好的,在时间和压缩率上达到平衡,更重要的是有很好的官方库支持,用起来很简单。其他的算法,速度快的压缩率很感人,压缩率声称比较高的,似乎也没高多少,唯一相对高的是 zpaq,但是时间真的慢到离谱,在很多项目上是不适配的,而且文档也没有多少,真正调用起来也要费点力气。

所以珍惜生命,使用 zstd 就好了,下载官方的库文件,然后调用就完事了。

// decompress.cpp
#include <stdio.h>
#include <stdlib.h>
#include <fstream>
#include <iostream>
#include <string.h>
#include "./zstd/include/zstd.h"

using namespace std;

// 读取文件内容,并且放在内存中
char* loadFile(const string filename) {
    // 读文件大小
    ifstream nowFile(filename, ios::in|ios::binary);
    nowFile.seekg(0, ios::end);
    size_t bufferSize = nowFile.tellg();
    nowFile.seekg(0, ios::beg);

    // 写入 Buffer
    char* nowBuffer = (char*) malloc(bufferSize * sizeof(char));
    nowFile.read(nowBuffer, bufferSize);
    nowFile.close();

    return nowBuffer;
}

// 获取解压后的第几帧的数据
char* decompressData(char* baseAddr, size_t offset, size_t cSize, size_t& rSize) {
    // 获取目前要解压的数据(根据开始地址、偏移量和大小读取)
    char* cBuffer = (char*) malloc(cSize * sizeof(char));
    memcpy(cBuffer, baseAddr+offset, cSize);

    // 计算解压后的大小
    rSize = ZSTD_getFrameContentSize(cBuffer, cSize);
    char* rBuffer = (char*) malloc(rSize * sizeof(char));

    // 解压
    ZSTD_decompress(rBuffer, rSize, cBuffer, cSize);

    free(cBuffer);
    return rBuffer;
}

// 写入文件
void writeFile(char* rBuffer, size_t oneSize, string dstname) {
    ofstream nowFile(dstname, ios::out|ios::binary);
    nowFile.write(rBuffer, oneSize);
    nowFile.close();
}

int main() {
    // 读取文件,该参数模拟在 Flash 中的基地址
    const string cName = "./test_decompress/compressed_data.zst";
    char* baseAddr = loadFile(cName);

    // 每个文件的开始相对基地址的偏移量(一个六个文件,但故意给了最后一个结束大小便于后面编码清楚一些)
    size_t begAddrs[7] = {
        0,
        10975912,
        27495915,
        36407837,
        53350923,
        78523184,
        89035705,    
    };

    // 故意打乱顺序验证通过索引解压的准确性
    size_t idxs[6] = {4, 2, 3, 0, 5, 1};

    // 循环进行,每次想要解压第 idxs[i] 帧,解压后写入文件
    string dstname = "./test_decompress/gendata/x.raw";
    for (int i = 0; i < 6; ++i) {
        // 现在要解压第几个索引
        size_t idx = idxs[i];

        // 现在这个索引:相对基地址的偏移量、压缩文件的大小
        size_t offset = begAddrs[idx];
        size_t nowCSize = begAddrs[idx+1]-begAddrs[idx];

        // 还要给一个参数,函数会赋给该参数为解压后的大小
        // 该参数用于表示函数体内使用 malloc 的内存大小
        size_t resultSize;
        char* resultBuffer = decompressData(baseAddr, offset, nowCSize, resultSize);

        // 写入文件
        dstname[dstname.length()-5] = ('0' + idx);
        writeFile(resultBuffer, resultSize, dstname);

        // 不需要用到解压后的数据了,一定要 Free 掉!!
        free(resultBuffer);
    }

    free(baseAddr);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <fstream>
#include <iostream>
#include <string.h>
#include "./zstd/include/zstd.h"

using namespace std;

// 读取文件内容,并且放在内存中
char* loadFile(const string filename) {
    // 读文件大小
    ifstream nowFile(filename, ios::in|ios::binary);
    nowFile.seekg(0, ios::end);
    size_t bufferSize = nowFile.tellg();
    nowFile.seekg(0, ios::beg);

    // 写入 Buffer
    char* nowBuffer = (char*) malloc(bufferSize * sizeof(char));
    nowFile.read(nowBuffer, bufferSize);
    nowFile.close();

    return nowBuffer;
}

// 获取解压后的第几帧的数据
char* decompressData(char* baseAddr, size_t offset, size_t cSize, size_t& rSize) {
    // 获取目前要解压的数据(根据开始地址、偏移量和大小读取)
    char* cBuffer = (char*) malloc(cSize * sizeof(char));
    memcpy(cBuffer, baseAddr+offset, cSize);

    // 计算解压后的大小
    rSize = ZSTD_getFrameContentSize(cBuffer, cSize);
    char* rBuffer = (char*) malloc(rSize * sizeof(char));

    // 解压
    ZSTD_decompress(rBuffer, rSize, cBuffer, cSize);

    free(cBuffer);
    return rBuffer;
}

// 写入文件
void writeFile(char* rBuffer, size_t oneSize, string dstname) {
    ofstream nowFile(dstname, ios::out|ios::binary);
    nowFile.write(rBuffer, oneSize);
    nowFile.close();
}

int main() {
    // 读取文件,该参数模拟在 Flash 中的基地址
    const string cName = "./test_decompress/compressed_data.zst";
    char* baseAddr = loadFile(cName);

    // 每个文件的开始相对基地址的偏移量(一个六个文件,但故意给了最后一个结束大小便于后面编码清楚一些)
    size_t begAddrs[7] = {
        0,
        10975912,
        27495915,
        36407837,
        53350923,
        78523184,
        89035705,    
    };

    // 故意打乱顺序验证通过索引解压的准确性
    size_t idxs[6] = {4, 2, 3, 0, 5, 1};

    // 循环进行,每次想要解压第 idxs[i] 帧,解压后写入文件
    string dstname = "./test_decompress/gendata/x.raw";
    for (int i = 0; i < 6; ++i) {
        // 现在要解压第几个索引
        size_t idx = idxs[i];

        // 现在这个索引:相对基地址的偏移量、压缩文件的大小
        size_t offset = begAddrs[idx];
        size_t nowCSize = begAddrs[idx+1]-begAddrs[idx];

        // 还要给一个参数,函数会赋给该参数为解压后的大小
        // 该参数用于表示函数体内使用 malloc 的内存大小
        size_t resultSize;
        char* resultBuffer = decompressData(baseAddr, offset, nowCSize, resultSize);

        // 写入文件
        dstname[dstname.length()-5] = ('0' + idx);
        writeFile(resultBuffer, resultSize, dstname);

        // 不需要用到解压后的数据了,一定要 Free 掉!!
        free(resultBuffer);
    }

    free(baseAddr);
    return 0;
}

Comments