使用libzip压缩文件和文件夹
简单说说自己遇到的坑:
- 分清楚三个组件:zlib、minizip和libzip。zlib是底层和最基础的C库,用于使用Deflate算法压缩和解压缩文件流或者单个文件,但是如果要压缩文件夹就很麻烦,主要是不知道如何归档,在zip内部形成对应的目录。这时就需要用更高级别的库,也就是minizip或libzip。
- minizip、libzip随着版本迭代接口一直变化,我连续使用了通义千问、文心一言、gemini三个AI,基本上没给出能使用的代码,主要是函数接口总是不对,或者参数多了或者少了。像这种情况就不要再参考AI给出的答案了,赶紧翻官方文档才是正经。
- minizip和libzip都是基于zlib实现的,都尝试使用过,感觉还是libzip的接口设计更清晰一点,官方文档说明也还不错。
- 压缩文件夹的功能需要借助于操作文件系统的库来组织zip内部的归档目录,我这里使用的是C++17的std::filesystem。
具体代码实现如下:
#include <zip.h> #include <filesystem> #include <fstream> #include <iostream> using namespace std; void CompressFile2Zip(std::filesystem::path unZipFilePath, const char* relativeName, zip_t* zipArchive) { std::ifstream file(unZipFilePath, std::ios::binary); file.seekg(0, std::ios::end); size_t bufferSize = file.tellg(); char* bufferData = (char*)malloc(bufferSize); file.seekg(0, std::ios::beg); file.read(bufferData, bufferSize); //第四个参数如果非0,会自动托管申请的资源,直到zip_close之前自动销毁。 zip_source_t* source = zip_source_buffer(zipArchive, bufferData, bufferSize, 1); if (source) { if (zip_file_add(zipArchive, relativeName, source, ZIP_FL_OVERWRITE) < 0) { std::cerr << "Failed to add file " << unZipFilePath << " to zip: " << zip_strerror(zipArchive) << std::endl; zip_source_free(source); } } else { std::cerr << "Failed to create zip source for " << unZipFilePath << ": " << zip_strerror(zipArchive) << std::endl; } } void CompressFile(std::filesystem::path unZipFilePath, std::filesystem::path zipFilePath) { int errorCode = 0; zip_t* zipArchive = zip_open(zipFilePath.generic_u8string().c_str(), ZIP_CREATE | ZIP_TRUNCATE, &errorCode); if (zipArchive) { CompressFile2Zip(unZipFilePath, unZipFilePath.filename().string().c_str(), zipArchive); errorCode = zip_close(zipArchive); if (errorCode != 0) { zip_error_t zipError; zip_error_init_with_code(&zipError, errorCode); std::cerr << zip_error_strerror(&zipError) << std::endl; zip_error_fini(&zipError); } } else { zip_error_t zipError; zip_error_init_with_code(&zipError, errorCode); std::cerr << "Failed to open output file " << zipFilePath << ": " << zip_error_strerror(&zipError) << std::endl; zip_error_fini(&zipError); } } void CompressDirectory2Zip(std::filesystem::path rootDirectoryPath, std::filesystem::path directoryPath, zip_t* zipArchive) { if (rootDirectoryPath != directoryPath) { if (zip_dir_add(zipArchive, std::filesystem::relative(directoryPath, rootDirectoryPath) .generic_u8string() .c_str(), ZIP_FL_ENC_UTF_8) < 0) { std::cerr << "Failed to add directory " << directoryPath << " to zip: " << zip_strerror(zipArchive) << std::endl; } } for (const auto& entry : std::filesystem::directory_iterator(directoryPath)) { if (entry.is_regular_file()) { CompressFile2Zip( entry.path().generic_u8string(), std::filesystem::relative(entry.path(), rootDirectoryPath) .generic_u8string() .c_str(), zipArchive); } else if (entry.is_directory()) { CompressDirectory2Zip(rootDirectoryPath, entry.path().generic_u8string(), zipArchive); } } } void CompressDirectory(std::filesystem::path directoryPath, std::filesystem::path zipFilePath) { int errorCode = 0; zip_t* zipArchive = zip_open(zipFilePath.generic_u8string().c_str(), ZIP_CREATE | ZIP_TRUNCATE, &errorCode); if (zipArchive) { CompressDirectory2Zip(directoryPath, directoryPath, zipArchive); errorCode = zip_close(zipArchive); if (errorCode != 0) { zip_error_t zipError; zip_error_init_with_code(&zipError, errorCode); std::cerr << zip_error_strerror(&zipError) << std::endl; zip_error_fini(&zipError); } } else { zip_error_t zipError; zip_error_init_with_code(&zipError, errorCode); std::cerr << "Failed to open output file " << zipFilePath << ": " << zip_error_strerror(&zipError) << std::endl; zip_error_fini(&zipError); } } int main() { //压缩文件 //CompressFile("C:/Data/Builder/Demo/view.tmp", "C:/Data/Builder/Demo/view.zip"); //压缩文件夹 CompressDirectory("C:/Data/Builder/Demo", "C:/Data/Builder/Demo.zip"); return 0; } CPP 折叠 复制 全屏
关于使用的libzip,有以下几点值得注意:
- libzip压缩的zip内部的文件名默认采用UTF-8编码。
- libzip要求使用正斜杠 ('/') 作为目录分隔符。
- libzip操作不同的zip线程安全,操作同一个zip线程不安全。
- zip_source_buffer这个函数的接口的第四个参数如果非0,会自动托管申请的资源。官方文档提到需要保证传入zip_source_buffer的数据资源需要保证跟zip_source_t一样的声明周期,但是笔者经过测试,正确的行为应该是传入zip_source_buffer的数据资源需要保证调用zip_close之前都有效,否则就有问题。