加载中...
libSnappy-快速压缩工具
第1节:libfswatch-文件变动监控
第2节:libiconv-字符集编码转换
第3节:CLI11-命令行参数解析
第4节:nlohmann/json-自然的JSON库
第5节:libb64-理解并玩转base64编码
第6节:libSnappy-快速压缩工具
课文封面

需要在极低资源占用的情况下,对数据做快速的压缩与解压,这种需求在软件系统中广泛存在。
来自 Google 公司的 libSnappy 正是为此类需求设计。

纯C++库,接口简单友好,基本一个 Compress(),一个 UnCompress() 解决一切。

1. 简介

Snappy,最早也称 “Zippy”,是 Google 公司基于 LZ77 的思路用 C++ 语言 编写 的快速数据压缩与解压的程序库。在 2011 年开源。

Snappy 的特点是非常高的速度和合理的压缩率。

以下是来自开发团队的自我介绍:

A light-weight compression algorithm. It is designed for speed of
compression and decompression, rather than for the utmost in space
savings.

翻译:一个轻量级的压缩算法,设计侧重于压缩和解压的速度,而非为了最大程度的节省空间。

简单地说,就是 “非常高的压缩速度和合理的压缩速率。”,以下是测试数据:

  • 运行在64位酷睿i7处理器的单个核心,压缩速度250 MB/s,解压速度500 MB/s;
  • 压缩率比 zlip 低 10-30%,但速度快 2 - 10 倍。

2. 安装

以 msys2 的 ucrt64 环境为例:pacman -S mingw-w64-ucrt-x86_64-snappy

3. 课堂视频

4. 课堂项目代码

  • CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.10) PROJECT(HelloLibSnappy LANGUAGES CXX) ADD_EXECUTABLE(${PROJECT_NAME} main.cpp) TARGET_LINK_LIBRARIES(${PROJECT_NAME} PRIVATE snappy.a)
  • main.cpp
#include <cstdlib> #include <iostream> #include <fstream> #include <snappy.h> // 测试压缩字符串,并将压缩结果写入指定的文件 void testCompress(std::string const& original_data, std::string const& filename) { // 先计算压缩后可能需要的最大字节数 size_t max_compressed_size = snappy::MaxCompressedLength(original_data.size()); std::string compressed_data (max_compressed_size, '\0'); // 预先分配好内存,并初始为 0 size_t compressed_size = snappy::Compress(original_data.data() , original_data.size(), &compressed_data); // 输出基本压缩信息: std::cout << "Original size : " << original_data.size() << "\nCompressed size : " << compressed_size // == compressed_data.size() << "\nCompressed ratio : " << static_cast<double>(compressed_size) / original_data.size() << std::endl; // 写入文件: std::ofstream ofs (filename, std::ios::binary); if (!ofs) { std::cerr << "Error : cannot open file : " << filename << std::endl; return; } ofs.write(compressed_data.data(), compressed_data.size()); } std::string testUncompress(std::string const& filename) { std::ifstream ifs(filename, std::ios::binary); if (!ifs) { return ""; } auto beg = std::istreambuf_iterator<std::ifstream::char_type>(ifs); auto end = std::istreambuf_iterator<std::ifstream::char_type>(); std::string compressed_data (beg, end); // 读入文件所有字符内容 ifs.close(); std::string original_data; bool ok = snappy::Uncompress(compressed_data.data(), compressed_data.size(), &original_data); if (!ok) { std::cerr << "Error : uncompress file fail : " << filename << std::endl; return ""; } return original_data; } int main() { std::system("chcp 65001 > nul"); std::cout << "\n=[压缩]================================================\n"; std::string src = R"(Snapy is a light-weight compression algorithm. It is designed for speed of compression and decompression, rather than for the utmost in space savings. For getting better compression ratios when you are compressing data with long repeated sequences or compressing data that is similar to other data, while still compressing fast, you might look at first using BMDiff and then compressing the output of BMDiff with Snappy. )"; std::cout << "original string : \n" << src << std::endl; std::string filename = "testString.snappy"; testCompress(src, filename); std::cout << "\n=[解压]================================================\n"; auto uncompressed = testUncompress(filename); std::cout << "uncompressed string : \n" << uncompressed << std::endl; std::system("pause"); }

5. 综合项目(大作业)

在本课作业中,需综合使用本课所学的 libSnappy 和前面课堂中所学的:

编写一个压缩、解压缩程序,生成独立的可执行程序 mySnappy.exe 。

写一个可有实际使用场景(包括给朋友使用)的工具软件,需要面对的各种问题、细节非常多,远不是日常课堂上的小练习所能比的。

5.1 基本要求

下面以使用案例的角度,描述该压缩工具的基本用法要求。

  • mySnappy a.txt → 将 a.txt 压缩为 a.txt.snappy;
  • mySnappy a.txt b.txt.snappy → 将 a.txt 压缩为 b.txt.snappy;
  • mySnappy -u a.txt.snappy → 解压 a.txt.snappya.txt;
  • mySnappy -u b.txt.snappy a.txt → 解压 b.txt.snappya.txt;
  • mySnappy --raw-encodeing 宣传.pptx → 压缩指定文件,且不对文件名包含的汉字做编码转换(编码要求详情见后);
  • mySnappy -f a.txt → 生成压缩文件 a.txt.snappy,如该文件已存在,将直接覆盖,不报错;
  • MySnappy -s a.txt → 仅在出错时往控制台输出信息。

即,除源文件名(必选)和目标文件名(可选)外,mySnappy.exe 还需支持以下 4 个标志(flag):

  1. -u 或 --uncompress:表示将执行解压操作,而非默认的压缩操作;
  2. –raw-encoding:表示将保持命令行中的文件路径名的原始编码,不作转换。如果文件路径名中没有包含汉字,或者虽然包含汉字,但它们都已经是 utf8 编码,可加上此标志(否则,路径中的汉字将被尝试从 GBK 编码,转换至 utf8 编码);
  3. -f 或 --force:如目标文件已经存在,不提供此标志,将直接报错退出,提供此标志,将直接覆盖原有文件(慎用!);
  4. -s 或 --silent:静默模式,即,仅在程序程序运行出错时输出信息。

5.2 路径要求

  • 相对路径转绝对路径
    应借助 std::filesystem ,实现将用户提供的路径(源文件和目标文件),都确保转换为绝对路径,比如,假设当前用户在 c:/abc/efg/ 目录下执行:mySnappy.exe ../a.txt ,最终被压缩的文件,应为 c:/abc/a.txt

  • 文件扩展名检查
    压缩时,目标文件扩展名应为 .snappy (Windows 下不区分大小写);解压缩时,源文件扩展名应为 .snappy(Windows 下不区分大小写)。

  • 自动推理目标文件名字
    压缩时,如未提供目标文件,程序应能自动将源文件加上 .snappy 作为目标文件名。解压缩时,如未提供目标文件,程序应能自动将源文件去除尾部的 .snappy ,作为目标文件名。

  • 源文件与目标文件重名判断
    如发现源文件与目标文件(包含路径)重名(Windows 下不区分大小写),程序应报错并退出。

  • 目标文件覆盖判断
    操作之前应先检查目标文件是否已经存在,如已存在并且命令行参数未提供 --force(或 -f ) 选项,程序应报错并退出。

5.3 编码转换说明

编码要求:

  • 程序源代码需使用 utf8 编码;
  • 程序启动后,应将所在控制台设置为 utf8 编码。

说明:

一、当我们在中文 Windows 的控制台下运行程序,并输入命令行参数,因为此时程序还没有运行,因此控制台仍然是国标编码,所以,引时在命令行内输入汉字,则汉字仍是国标编码(比如 GB2312、GBK 等),程序应将其转换为 utf8 编码。

二、我们在 vscode 内运行(包括调试)程序,如果在设置中为程序添加了命令行参数( args ),通常所在设置文件编码为 utf8,因此,此时的命令行参数如包含汉字,也无需进行编码转换,否则反倒会出错(此时可用上 --raw-encoding 选项)。

三、标准库 std::filesystem 同样只支持 utf8 编码,但是在 Windows 平台下,它将负责在生成磁盘文件前,把文件名转换为 GB 编码,避免生成名字包含乱码的文件。

5.4 std::filesystem 相关功能

你可能需要用到 std::filesystem 的以下功能。

  • 使用 std::filesystem::path (以下简称 path ) 而非 std::string 来表达文件路径及名字,以获得更好的路径支持;
  • 调用 path 对象的 .string() 方法可将其转换为字符串;
  • path 对象的 .extension() 方法可得到其扩展名(包含 .),它同样是一个 path 对象;
  • path 对象的 .replace_extension(newExt) 方法可将其扩展名更改为 newExt,如 newExt 为空,相当于去除其原有(最后一级)的扩展名;
  • std::filesystem::absolute(srcPath),得到一个新的 path 对象,它使用绝对路径表达 srcPath
  • std::filesystem::exists(filename),检查 filename 指定的文件是否已经存在。

更多设计方面的要求,及完整示范代码 (提交作业后可见),见本课 课堂作业