压缩算法,常被称作“数字魔法”,能在不损失信息的前提下,极大地缩减数据体积。对于存储、算力和带宽都极为有限的嵌入式设备而言,这项技术是不可或缺的“利器”。选择合适的压缩方案,往往能直接改善设备的性能、降低功耗并优化成本。
常用压缩格式对比
面对众多压缩格式,嵌入式开发者该如何选择?我们不妨先通过下表,对几种常用格式的核心特性有一个直观的了解:
| 压缩格式 |
算法类型 |
压缩率 |
压缩速度 |
解压速度 |
内存占用 |
平台支持 |
适用场景 |
| ZIP |
DEFLATE |
中等 |
较快 |
快 |
低 |
全平台 |
通用文件传输、跨平台兼容性要求高 |
| TAR.GZ |
GZIP + TAR |
高 |
中等 |
中等 |
低 |
Unix/Linux |
系统备份、源码分发 |
| 7Z |
LZMA/LZMA2 |
极高 |
慢 |
中等 |
中 |
多平台 |
空间受限场景、大型文件压缩 |
| RAR |
RAR |
高 |
中等 |
中等 |
中 |
Windows为主 |
Windows平台文件压缩 |
| XZ |
LZMA2 |
极高 |
慢 |
中等 |
中 |
Unix/Linux |
软件包分发、长期存储 |
| ZSTD |
Zstandard |
高 |
极快 |
极快 |
低 |
多平台 |
实时数据压缩、流式传输 |
在嵌入式系统中的典型应用场景与选择策略
1. 固件升级包压缩
应用场景:嵌入式设备的远程无线(OTA)或有线固件升级。压缩可以显著减小传输包体积,节省带宽与流量,并加快下载速度。
选择策略:
- 中小容量设备(< 1MB):优先使用 ZIP 格式。其解压速度快、内存占用低,对资源受限的设备非常友好。
- 大容量设备(> 1MB):推荐使用 7Z 或 XZ 格式。它们能提供极高的压缩率,在传输大型固件时优势明显。
- 对升级实时性要求极高:可以考虑 ZSTD,它的解压速度极快,能最大限度缩短设备因解压而等待的时间。
应用示例:
下面是一个使用轻量级库 miniz 解压 ZIP 格式固件包的 C 语言示例:
// 基于miniz的轻量级ZIP解压实现
#include "miniz.h"
int extract_firmware(const char *zip_path, const char *dest_dir) {
mz_zip_archive zip;
memset(&zip, 0, sizeof(zip));
if (!mz_zip_reader_init_file(&zip, zip_path, 0)) {
return -1;
}
// 解压所有文件
for (int i = 0; i < mz_zip_reader_get_num_files(&zip); i++) {
mz_zip_archive_file_stat file_stat;
if (mz_zip_reader_file_stat(&zip, i, &file_stat)) {
char dest_path[256];
snprintf(dest_path, sizeof(dest_path), "%s/%s", dest_dir, file_stat.m_filename);
mz_zip_reader_extract_to_file(&zip, i, dest_path, 0);
}
}
mz_zip_reader_end(&zip);
return 0;
}
2. 配置文件与数据备份压缩
应用场景:设备配置参数的备份与恢复,或用户历史数据的本地归档。压缩可以有效节约宝贵的非易失存储空间(如 Flash)。
选择策略:
- 配置文件:优先使用 ZIP。格式通用,在桌面端和嵌入式端都容易处理,兼容性好。
- 批量历史数据备份:考虑 TAR.GZ。适合将多个日志文件或数据文件打包后再压缩。
- 存储空间极度受限:使用 7Z 来获得最高的压缩率。
应用示例:
在开发或维护工具链中,我们常用 Python 脚本进行配置的打包与备份:
import zipfile
import os
def backup_configs(config_dir, backup_file):
"""备份配置目录到ZIP文件"""
with zipfile.ZipFile(backup_file, 'w', zipfile.ZIP_DEFLATED) as zf:
for root, dirs, files in os.walk(config_dir):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, config_dir)
zf.write(file_path, arcname)
def restore_configs(backup_file, restore_dir):
"""从ZIP文件恢复配置"""
with zipfile.ZipFile(backup_file, 'r') as zf:
zf.extractall(restore_dir)
3. 日志文件压缩
应用场景:设备在运行过程中会产生大量日志,压缩后再存储或上传,能大幅降低对存储介质的磨损和网络带宽的占用。
选择策略:
- 实时压缩(日志边生成边压缩):选用 ZSTD 或 LZ4,它们拥有无与伦比的压缩速度。
- 定时批量压缩:可使用 TAR.GZ 或 XZ,追求更高的压缩率。
- 网络传输优化:需根据实际的网络带宽情况,在压缩速度与压缩率之间权衡,选择合适的算法和级别。
应用示例:
使用 ZSTD 库对日志文件进行高效压缩的 C 语言实现:
// 使用zstd进行日志文件压缩
#include <zstd.h>
#include <stdio.h>
#include <stdlib.h>
int compress_log_file(const char *input_file, const char *output_file) {
FILE *in = fopen(input_file, "rb");
FILE *out = fopen(output_file, "wb");
if (!in || !out) return -1;
ZSTD_CCtx *cctx = ZSTD_createCCtx();
size_t const buffInSize = ZSTD_CStreamInSize();
size_t const buffOutSize = ZSTD_CStreamOutSize();
void * const inBuff = malloc(buffInSize);
void * const outBuff = malloc(buffOutSize);
size_t readSize;
while ((readSize = fread(inBuff, 1, buffInSize, in)) > 0) {
ZSTD_inBuffer input = { inBuff, readSize, 0 };
while (input.pos < input.size) {
ZSTD_outBuffer output = { outBuff, buffOutSize, 0 };
size_t const ret = ZSTD_compressStream2(cctx, &output, &input, ZSTD_e_continue);
if (ZSTD_isError(ret)) return -1;
fwrite(outBuff, 1, output.pos, out);
}
}
// 刷新剩余数据
ZSTD_inBuffer input = { inBuff, 0, 0 };
while (true) {
ZSTD_outBuffer output = { outBuff, buffOutSize, 0 };
size_t const ret = ZSTD_compressStream2(cctx, &output, &input, ZSTD_e_end);
if (ZSTD_isError(ret)) return -1;
fwrite(outBuff, 1, output.pos, out);
if (ret == 0) break;
}
ZSTD_freeCCtx(cctx);
free(inBuff);
free(outBuff);
fclose(in);
fclose(out);
return 0;
}
4. 资源文件打包
应用场景:嵌入式 GUI 应用或多媒体设备中,需要将图片、字体、音频等资源文件打包进固件。
选择策略:
- 对应用启动时间敏感:选择低压缩率的 ZIP,甚至不压缩,以确保资源能被快速解压或直接读取。
- 存储空间是主要瓶颈:使用高压缩率的 7Z 或 XZ。
- 需要考虑跨平台编辑和兼容:ZIP 格式依然是首选。
应用示例:
在构建阶段,使用 Python 脚本自动化打包资源文件:
# 使用Python脚本打包资源文件
import zipfile
import os
def package_resources(resource_dir, output_zip):
"""打包资源目录为ZIP文件"""
with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zf:
for root, dirs, files in os.walk(resource_dir):
for file in files:
if file.endswith(('.png', '.jpg', '.ttf', '.wav')):
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, resource_dir)
zf.write(file_path, arcname)
print(f"Resources packed to {output_zip}")
# 在构建脚本中调用
if __name__ == "__main__":
package_resources("resources", "app_resources.zip")
嵌入式常用压缩库推荐
在嵌入式环境下,我们通常需要一些轻量级、可裁剪、无外部依赖的压缩库。
轻量级压缩库选型
| 库名称 |
支持格式 |
特点 |
适用场景 |
| miniz |
ZIP |
单文件,无依赖,极其轻量 |
嵌入式设备、资源极度受限场景 |
| zlib |
DEFLATE |
广泛使用,极其稳定 |
通用的压缩/解压需求,GZIP基础 |
| LZ4 |
LZ4 |
极快的压缩/解压速度 |
实时数据压缩,对速度要求极高的场景 |
| ZSTD |
Zstandard |
在压缩率与速度间取得优秀平衡 |
现代嵌入式应用的首选,性能全面 |
| libarchive |
TAR, ZIP, 7Z 等 |
支持多种格式,功能全面 |
需要处理多种压缩/归档格式的需求 |
| bit7z |
7Z, ZIP, RAR 等 |
7-Zip SDK 的 C++ 封装 |
需要 7-Zip 高级功能的项目 |
库的集成与使用示例
miniz 集成示例(解压):
// 解压ZIP文件
#include "miniz.h"
#include <stdio.h>
int unzip_file(const char *zip_filename, const char *dest_dir) {
mz_zip_archive zip_archive;
memset(&zip_archive, 0, sizeof(zip_archive));
// 初始化ZIP读取器
if (!mz_zip_reader_init_file(&zip_archive, zip_filename, 0)) {
printf("Failed to initialize zip reader\n");
return -1;
}
// 获取文件数量
int file_count = mz_zip_reader_get_num_files(&zip_archive);
// 解压每个文件
for (int i = 0; i < file_count; i++) {
mz_zip_archive_file_stat file_stat;
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) {
continue;
}
// 构建目标路径
char dest_path[256];
snprintf(dest_path, sizeof(dest_path), "%s/%s", dest_dir, file_stat.m_filename);
// 解压文件
if (!mz_zip_reader_extract_to_file(&zip_archive, i, dest_path, 0)) {
printf("Failed to extract file: %s\n", file_stat.m_filename);
}
}
// 清理
mz_zip_reader_end(&zip_archive);
return 0;
}
ZSTD 集成示例(压缩):
// 压缩文件
#include <zstd.h>
#include <stdio.h>
#include <stdlib.h>
int compress_file_zstd(const char *input_filename, const char *output_filename, int compression_level) {
FILE *in = fopen(input_filename, "rb");
if (!in) return -1;
FILE *out = fopen(output_filename, "wb");
if (!out) {
fclose(in);
return -1;
}
// 创建压缩上下文
ZSTD_CCtx *cctx = ZSTD_createCCtx();
if (!cctx) {
fclose(in);
fclose(out);
return -1;
}
// 设置压缩级别
ZSTD_CCtx_setParameter(cctx, ZSTD_p_compressionLevel, compression_level);
// 压缩循环
size_t const buffInSize = ZSTD_CStreamInSize();
size_t const buffOutSize = ZSTD_CStreamOutSize();
void* const inBuff = malloc(buffInSize);
void* const outBuff = malloc(buffOutSize);
size_t readSize;
while ((readSize = fread(inBuff, 1, buffInSize, in)) > 0) {
ZSTD_inBuffer input = { inBuff, readSize, 0 };
while (input.pos < input.size) {
ZSTD_outBuffer output = { outBuff, buffOutSize, 0 };
size_t const ret = ZSTD_compressStream2(cctx, &output, &input, ZSTD_e_continue);
if (ZSTD_isError(ret)) {
free(inBuff);
free(outBuff);
ZSTD_freeCCtx(cctx);
fclose(in);
fclose(out);
return -1;
}
fwrite(outBuff, 1, output.pos, out);
}
}
// 刷新剩余数据
ZSTD_inBuffer input = { inBuff, 0, 0 };
while (true) {
ZSTD_outBuffer output = { outBuff, buffOutSize, 0 };
size_t const ret = ZSTD_compressStream2(cctx, &output, &input, ZSTD_e_end);
if (ZSTD_isError(ret)) {
free(inBuff);
free(outBuff);
ZSTD_freeCCtx(cctx);
fclose(in);
fclose(out);
return -1;
}
fwrite(outBuff, 1, output.pos, out);
if (ret == 0) break;
}
// 清理
free(inBuff);
free(outBuff);
ZSTD_freeCCtx(cctx);
fclose(in);
fclose(out);
return 0;
}
总结
在嵌入式系统设计中,合理地选择和应用压缩算法,能带来立竿见影的收益:
- 节省存储空间:通过高压缩率算法,直接减少 Flash 或 EEPROM 的数据占用,降低硬件成本。
- 提升传输效率:在 OTA 升级或数据上报时,减小网络数据包体积,节省流量并加快传输速度。
- 优化系统性能与功耗:选择解压速度快、内存占用低的算法,可以避免压缩/解压操作成为系统瓶颈,同时减少因频繁读写存储介质带来的功耗。
关键是要根据具体的应用场景(固件、日志、配置、资源)、设备约束(内存、算力、存储)和性能要求(压缩率、速度),在众多优秀的算法和库中做出最恰当的权衡与选择。希望本文的分析和示例能为你下一次的嵌入式开发决策提供有价值的参考。如果你想了解更多嵌入式或底层开发技术,欢迎来到云栈社区与其他开发者交流探讨。