第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,我推荐两种方式:

  1. FetchContent(推荐,自动下载编译)
  2. 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集成与使用流程 CMake集成 FetchContent / find_package 编译链接 target_link_libraries(lz4::liblz4) API调用 LZ4_compress_default CMake集成细节 • FetchContent_Declare + MakeAvailable • 自动下载、编译、生成目标 • 无需手动指定头文件路径 • 支持指定版本(GIT_TAG) • 跨平台,不依赖系统环境 • 推荐用于团队协作项目 API使用细节 • LZ4_compressBound:计算最大输出大小 • LZ4_compress_default:快速压缩 • LZ4_decompress_safe:安全解压 • 返回值:压缩/解压后的字节数 • 负值表示失败 • 解压缓冲区必须足够大

避坑指南

我曾经踩过的坑:

  • 缓冲区大小不够:解压时,目标缓冲区必须 >= 原始数据大小。否则 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的集成其实很简单,核心就三步:

  1. 用FetchContent拉取源码
  2. 链接 lz4::liblz4
  3. 调用 LZ4_compress_defaultLZ4_decompress_safe

难的是理解它的适用场景。记住:LZ4不是用来替代zlib的,它是用来“快”的。如果你需要极致压缩率,请用zstd或xz;如果你需要极致速度,LZ4不会让你失望。

好了,这一讲就到这里。代码不多,但都是实战经验。下次你在项目中遇到性能瓶颈时,不妨试试LZ4——说不定有惊喜。


公众号:蓝海资料掘金营,微信deep3321