使用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;
}
關于使用的libzip,有以下幾點值得注意:
- libzip壓縮的zip內部的文件名默認采用UTF-8編碼。
- libzip要求使用正斜杠 ('/') 作為目錄分隔符。
- libzip操作不同的zip線程安全,操作同一個zip線程不安全。
- zip_source_buffer這個函數的接口的第四個參數如果非0,會自動托管申請的資源。官方文檔提到需要保證傳入zip_source_buffer的數據資源需要保證跟zip_source_t一樣的聲明周期,但是筆者經過測試,正確的行為應該是傳入zip_source_buffer的數據資源需要保證調用zip_close之前都有效,否則就有問題。

浙公網安備 33010602011771號