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 和前面课堂中所学的:
- libiconv-编码转换库 (第 2 杰);
- libCLI11-命令行解析库 (第 3 杰);
- 以及 C++ 17 引入的 std::filesystem 文件 & 路径
编写一个压缩、解压缩程序,生成独立的可执行程序 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.snappy
为a.txt
;mySnappy -u b.txt.snappy a.txt
→ 解压b.txt.snappy
为a.txt
;mySnappy --raw-encodeing 宣传.pptx
→ 压缩指定文件,且不对文件名包含的汉字做编码转换(编码要求详情见后);mySnappy -f a.txt
→ 生成压缩文件a.txt.snappy
,如该文件已存在,将直接覆盖,不报错;MySnappy -s a.txt
→ 仅在出错时往控制台输出信息。
即,除源文件名(必选)和目标文件名(可选)外,mySnappy.exe 还需支持以下 4 个标志(flag):
- -u 或 --uncompress:表示将执行解压操作,而非默认的压缩操作;
- –raw-encoding:表示将保持命令行中的文件路径名的原始编码,不作转换。如果文件路径名中没有包含汉字,或者虽然包含汉字,但它们都已经是 utf8 编码,可加上此标志(否则,路径中的汉字将被尝试从 GBK 编码,转换至 utf8 编码);
- -f 或 --force:如目标文件已经存在,不提供此标志,将直接报错退出,提供此标志,将直接覆盖原有文件(慎用!);
- -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
指定的文件是否已经存在。
更多设计方面的要求,及完整示范代码 (提交作业后可见),见本课 课堂作业 。