第86例:LZ4——集成LZ4快速压缩库
压缩库这东西,我这些年用过不少。zlib、zstd、bzip2……各有各的脾气。但如果你问我,什么场景下最爽快?我会说——追求极致速度,又不想牺牲太多压缩率的时候,LZ4是首选。
LZ4号称“最快压缩库”,解压速度能达到单核4GB/s。你想想看,这什么概念?基本上不拖IO后腿。我在做实时日志采集系统时,就用它来压缩网络传输的数据包,效果立竿见影。
今天我们就来实战:如何在CMake项目中优雅地集成LZ4。
LZ4是什么?
LZ4是一种无损压缩算法,由Yann Collet开发。它的核心设计理念就一个字——快。
- 压缩速度:约500 MB/s(单核)
- 解压速度:约4 GB/s(单核)
- 压缩率:通常2:1左右(文本数据)
说白了,它不追求极致压缩比,而是追求“压缩和解压都快到没感觉”。
适用场景:
- 实时数据流压缩(网络传输、日志)
- 内存数据库的页压缩
- 游戏资源包的快速加载
- 嵌入式设备(代码量小、内存占用低)
CMake集成方式
集成LZ4,我推荐两种方式:
- FetchContent(推荐,自动下载编译)
- find_package(系统已安装LZ4时使用)
我个人习惯用FetchContent。为什么?因为不依赖系统环境,项目拉到哪都能编译。我曾经在客户的CentOS 6机器上折腾过系统库版本问题,从那以后,能自带的库绝不依赖系统。
方式一:FetchContent(最省心)
cmake_minimum_required(VERSION 3.14)
project(LZ4Demo C)
# 引入FetchContent模块
include(FetchContent)
# 声明LZ4
FetchContent_Declare(
lz4
GIT_REPOSITORY https://github.com/lz4/lz4.git
GIT_TAG v1.9.4
)
# 获取并构建
FetchContent_MakeAvailable(lz4)
# 添加可执行文件
add_executable(demo main.c)
# 链接LZ4库
target_link_libraries(demo PRIVATE lz4::liblz4)
嗯,这里要注意:FetchContent_MakeAvailable 会直接编译LZ4的源码,生成静态库。你不需要手动指定头文件路径,CMake会自动处理好。
方式二:find_package(系统已安装时)
find_package(lz4 REQUIRED)
add_executable(demo main.c)
target_link_libraries(demo PRIVATE lz4::liblz4)
这种方式的前提是系统已经安装了LZ4开发包。Ubuntu下可以 sudo apt install liblz4-dev。但说实话,我不太推荐——你没法保证每个开发者的环境都一样。
实战:压缩与解压
我们写一个简单的demo:压缩一段字符串,再解压回来。
// main.c
#include <stdio.h>
#include <string.h>
#include <lz4.h>
int main() {
const char* src = "Hello, LZ4! 这是CMake实战100例的第86例。";
int src_size = strlen(src) + 1; // 包含结尾'\0'
// 计算最大压缩后大小
int max_dst_size = LZ4_compressBound(src_size);
char* compressed = malloc(max_dst_size);
// 压缩
int compressed_size = LZ4_compress_default(src, compressed, src_size, max_dst_size);
if (compressed_size <= 0) {
printf("压缩失败\n");
return 1;
}
printf("原始大小: %d 字节\n", src_size);
printf("压缩后大小: %d 字节\n", compressed_size);
printf("压缩率: %.2f%%\n", (float)compressed_size / src_size * 100);
// 解压
char* decompressed = malloc(src_size);
int decompressed_size = LZ4_decompress_safe(compressed, decompressed, compressed_size, src_size);
if (decompressed_size < 0) {
printf("解压失败\n");
return 1;
}
printf("解压后: %s\n", decompressed);
free(compressed);
free(decompressed);
return 0;
}
运行结果大致是:
原始大小: 47 字节
压缩后大小: 33 字节
压缩率: 70.21%
解压后: Hello, LZ4! 这是CMake实战100例的第86例。
你看,对于这种短文本,压缩率70%左右。如果是大段重复文本,压缩率能到20%以下。
小技巧:LZ4_compressBound() 返回的是最坏情况下的压缩后大小。实际压缩后大小可能比这个小很多。所以分配缓冲区时,用这个函数最安全。
SVG:LZ4集成与使用流程
下面这张图,帮你理清整个集成和使用的脉络:
避坑指南
我曾经踩过的坑:
- 缓冲区大小不够:解压时,目标缓冲区必须 >= 原始数据大小。否则
LZ4_decompress_safe会返回负数。我一开始用LZ4_compressBound的结果来分配解压缓冲区,结果解压大文件时崩了——因为压缩后的数据可能比原始数据还大(极端情况)。 - 忘记包含头文件:
#include <lz4.h>是必须的。如果你用C++,记得加extern "C"吗?其实LZ4的头文件已经帮你处理好了,放心用。 - 版本不匹配:如果系统装了旧版LZ4,你又用FetchContent拉新版,链接时可能冲突。建议统一用FetchContent,或者统一用系统库。
进阶:流式压缩
如果你要压缩大文件(比如几百MB),一次性压缩会占用大量内存。LZ4提供了流式接口:
LZ4_stream_t* stream = LZ4_createStream();
// 循环读取数据块,逐块压缩
LZ4_compress_fast_continue(stream, src, dst, src_size, dst_max_size, acceleration);
LZ4_freeStream(stream);
流式压缩的好处是:内存占用固定,适合嵌入式或流媒体场景。我在做车载日志采集时就用过,内存只占几十KB,稳得很。
总结
LZ4的集成其实很简单,核心就三步:
- 用FetchContent拉取源码
- 链接
lz4::liblz4 - 调用
LZ4_compress_default和LZ4_decompress_safe
难的是理解它的适用场景。记住:LZ4不是用来替代zlib的,它是用来“快”的。如果你需要极致压缩率,请用zstd或xz;如果你需要极致速度,LZ4不会让你失望。
好了,这一讲就到这里。代码不多,但都是实战经验。下次你在项目中遇到性能瓶颈时,不妨试试LZ4——说不定有惊喜。