Zlib是一個開源的資料壓縮庫,提供了一種通用的資料壓縮和解壓縮演演算法。它最初由Jean-Loup Gailly
和Mark Adler
開發,旨在成為一個高效、輕量級的壓縮庫,其被廣泛應用於許多領域,包括網路通訊、檔案壓縮、資料庫系統等。其壓縮演演算法是基於DEFLATE
演演算法,這是一種無失真資料壓縮演演算法,通常能夠提供相當高的壓縮比。
在Zlib專案中的contrib
目錄下有一個minizip
子專案,minizip實際上不是zlib
庫的一部分,而是一個獨立的開源庫,用於處理ZIP壓縮檔案格式。它提供了對ZIP檔案的建立和解壓的簡單介面。minizip在很多情況下與zlib
一起使用,因為ZIP壓縮通常使用了DEFLATE
壓縮演演算法。通過對minizip
庫的二次封裝則可實現針對目錄的壓縮與解壓功能。
如果你想使用minizip
通常你需要下載並編譯它,然後將其連結到你的專案中。
編譯Zlib庫很簡單,解壓檔案並進入到\zlib-1.3\contrib\vstudio
目錄下,根據自己編譯器版本選擇不同的目錄,這裡我選擇vc12
,進入後開啟zlibvc.sln
等待生成即可。
成功後可獲得兩個檔案分別是zlibstat.lib
和zlibwapi.lib
如下圖;
接著設定參照目錄,這裡需要多設定一個minizip
標頭檔案,該標頭檔案是zlib裡面的一個子專案。
lib庫則需要包含zlibstat.lib
和zlibwapi.lib
這兩個檔案,此處讀者可以自行放入到一個目錄下;
如下所示程式碼是一個使用zlib庫實現的簡單資料夾壓縮工具的C++程式。該程式提供了壓縮資料夾到 ZIP 檔案的功能,支援遞迴地新增檔案和子資料夾,利用了 Windows API 和 zlib 庫的函數。
#define ZLIB_WINAPI
#include <string>
#include <iostream>
#include <vector>
#include <Shlwapi.h>
#include <zip.h>
#include <unzip.h>
#include <zlib.h>
using namespace std;
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "zlibstat.lib")
bool nyAddfiletoZip(zipFile zfile, const std::string& fileNameinZip, const std::string& srcfile)
{
// 目錄如果為空則直接返回
if (NULL == zfile || fileNameinZip.empty())
{
return 0;
}
int nErr = 0;
zip_fileinfo zinfo = { 0 };
tm_zip tmz = { 0 };
zinfo.tmz_date = tmz;
zinfo.dosDate = 0;
zinfo.internal_fa = 0;
zinfo.external_fa = 0;
char sznewfileName[MAX_PATH] = { 0 };
memset(sznewfileName, 0x00, sizeof(sznewfileName));
strcat_s(sznewfileName, fileNameinZip.c_str());
if (srcfile.empty())
{
strcat_s(sznewfileName, "\\");
}
nErr = zipOpenNewFileInZip(zfile, sznewfileName, &zinfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION);
if (nErr != ZIP_OK)
{
return false;
}
if (!srcfile.empty())
{
// 開啟原始檔
FILE* srcfp = _fsopen(srcfile.c_str(), "rb", _SH_DENYNO);
if (NULL == srcfp)
{
std::cout << "開啟原始檔失敗" << std::endl;
return false;
}
// 讀入原始檔寫入zip檔案
int numBytes = 0;
char* pBuf = new char[1024 * 100];
if (NULL == pBuf)
{
std::cout << "新建緩衝區失敗" << std::endl;
return 0;
}
while (!feof(srcfp))
{
memset(pBuf, 0x00, sizeof(pBuf));
numBytes = fread(pBuf, 1, sizeof(pBuf), srcfp);
nErr = zipWriteInFileInZip(zfile, pBuf, numBytes);
if (ferror(srcfp))
{
break;
}
}
delete[] pBuf;
fclose(srcfp);
}
zipCloseFileInZip(zfile);
return true;
}
bool nyCollectfileInDirtoZip(zipFile zfile, const std::string& filepath, const std::string& parentdirName)
{
if (NULL == zfile || filepath.empty())
{
return false;
}
bool bFile = false;
std::string relativepath = "";
WIN32_FIND_DATAA findFileData;
char szpath[MAX_PATH] = { 0 };
if (::PathIsDirectoryA(filepath.c_str()))
{
strcpy_s(szpath, sizeof(szpath) / sizeof(szpath[0]), filepath.c_str());
int len = strlen(szpath) + strlen("\\*.*") + 1;
strcat_s(szpath, len, "\\*.*");
}
else
{
bFile = true;
strcpy_s(szpath, sizeof(szpath) / sizeof(szpath[0]), filepath.c_str());
}
HANDLE hFile = ::FindFirstFileA(szpath, &findFileData);
if (NULL == hFile)
{
return false;
}
do
{
if (parentdirName.empty())
relativepath = findFileData.cFileName;
else
// 生成zip檔案中的相對路徑
relativepath = parentdirName + "\\" + findFileData.cFileName;
// 如果是目錄
if (findFileData.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
{
// 去掉目錄中的.當前目錄和..前一個目錄
if (strcmp(findFileData.cFileName, ".") != 0 && strcmp(findFileData.cFileName, "..") != 0)
{
nyAddfiletoZip(zfile, relativepath, "");
char szTemp[MAX_PATH] = { 0 };
strcpy_s(szTemp, filepath.c_str());
strcat_s(szTemp, "\\");
strcat_s(szTemp, findFileData.cFileName);
nyCollectfileInDirtoZip(zfile, szTemp, relativepath);
}
continue;
}
char szTemp[MAX_PATH] = { 0 };
if (bFile)
{
//注意:處理單獨檔案的壓縮
strcpy_s(szTemp, filepath.c_str());
}
else
{
//注意:處理目錄檔案的壓縮
strcpy_s(szTemp, filepath.c_str());
strcat_s(szTemp, "\\");
strcat_s(szTemp, findFileData.cFileName);
}
nyAddfiletoZip(zfile, relativepath, szTemp);
} while (::FindNextFileA(hFile, &findFileData));
FindClose(hFile);
return true;
}
/*
* 函數功能 : 壓縮資料夾到目錄
* 備 注 : dirpathName 原始檔/資料夾
* zipFileName 目的壓縮包
* parentdirName 壓縮包內名字(資料夾名)
*/
bool nyCreateZipfromDir(const std::string& dirpathName, const std::string& zipfileName, const std::string& parentdirName)
{
bool bRet = false;
/*
APPEND_STATUS_CREATE 建立追加
APPEND_STATUS_CREATEAFTER 建立後追加(覆蓋方式)
APPEND_STATUS_ADDINZIP 直接追加
*/
zipFile zFile = NULL;
if (!::PathFileExistsA(zipfileName.c_str()))
{
zFile = zipOpen(zipfileName.c_str(), APPEND_STATUS_CREATE);
}
else
{
zFile = zipOpen(zipfileName.c_str(), APPEND_STATUS_ADDINZIP);
}
if (NULL == zFile)
{
std::cout << "建立ZIP檔案失敗" << std::endl;
return bRet;
}
if (nyCollectfileInDirtoZip(zFile, dirpathName, parentdirName))
{
bRet = true;
}
zipClose(zFile, NULL);
return bRet;
}
nyCreateZipfromDir函數
bool nyCreateZipfromDir(const std::string& dirpathName, const std::string& zipfileName, const std::string& parentdirName);
功能:壓縮資料夾到指定的 ZIP 檔案。
引數:
nyCollectfileInDirtoZip 函數
bool nyCollectfileInDirtoZip(zipFile zfile, const std::string& filepath, const std::string& parentdirName);
功能:遞迴地收集資料夾中的檔案,並將它們新增到已開啟的 ZIP 檔案中。
引數:
nyAddfiletoZip 函數
bool nyAddfiletoZip(zipFile zfile, const std::string& fileNameinZip, const std::string& srcfile);
功能:將指定檔案新增到已開啟的 ZIP 檔案中。
引數:
程式流程
int main(int argc, char* argv[])
{
std::string dirpath = "D:\\lyshark\\test"; // 原始檔/資料夾
std::string zipfileName = "D:\\lyshark\\test.zip"; // 目的壓縮包
bool ref = nyCreateZipfromDir(dirpath, zipfileName, "lyshark"); // 包內檔名<如果為空則壓縮時不指定目錄>
std::cout << "[LyShark] 壓縮狀態 " << ref << std::endl;
system("pause");
return 0;
}
上述呼叫程式碼,引數1指定為需要壓縮的檔案目錄,引數2指定為需要壓縮成目錄名,引數3為壓縮後該目錄的名字。
在這個C++程式中,實現了遞迴解壓縮ZIP檔案的功能。程式提供了以下主要功能:
#define ZLIB_WINAPI
#include <string>
#include <iostream>
#include <vector>
#include <Shlwapi.h>
#include <zip.h>
#include <unzip.h>
#include <zlib.h>
using namespace std;
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "zlibstat.lib")
// 將字串內的old_value替換成new_value
std::string& replace_all(std::string& str, const std::string& old_value, const std::string& new_value)
{
while (true)
{
std::string::size_type pos(0);
if ((pos = str.find(old_value)) != std::string::npos)
str.replace(pos, old_value.length(), new_value);
else
break;
}
return str;
}
// 建立多級目錄
BOOL CreatedMultipleDirectory(const std::string& direct)
{
std::string Directoryname = direct;
if (Directoryname[Directoryname.length() - 1] != '\\')
{
Directoryname.append(1, '\\');
}
std::vector< std::string> vpath;
std::string strtemp;
BOOL bSuccess = FALSE;
for (int i = 0; i < Directoryname.length(); i++)
{
if (Directoryname[i] != '\\')
{
strtemp.append(1, Directoryname[i]);
}
else
{
vpath.push_back(strtemp);
strtemp.append(1, '\\');
}
}
std::vector< std::string>::iterator vIter = vpath.begin();
for (; vIter != vpath.end(); vIter++)
{
bSuccess = CreateDirectoryA(vIter->c_str(), NULL) ? TRUE : FALSE;
}
return bSuccess;
}
/*
* 函數功能 : 遞迴解壓檔案目錄
* 備 注 : strFilePath 壓縮包路徑
* strTempPath 解壓到
*/
void UnzipFile(const std::string& strFilePath, const std::string& strTempPath)
{
int nReturnValue;
string tempFilePath;
string srcFilePath(strFilePath);
string destFilePath;
// 開啟zip檔案
unzFile unzfile = unzOpen(srcFilePath.c_str());
if (unzfile == NULL)
{
return;
}
// 獲取zip檔案的資訊
unz_global_info* pGlobalInfo = new unz_global_info;
nReturnValue = unzGetGlobalInfo(unzfile, pGlobalInfo);
if (nReturnValue != UNZ_OK)
{
std::cout << "封包: " << pGlobalInfo->number_entry << endl;
return;
}
// 解析zip檔案
unz_file_info* pFileInfo = new unz_file_info;
char szZipFName[MAX_PATH] = { 0 };
char szExtraName[MAX_PATH] = { 0 };
char szCommName[MAX_PATH] = { 0 };
// 存放從zip中解析出來的內部檔名
for (int i = 0; i < pGlobalInfo->number_entry; i++)
{
// 解析得到zip中的檔案資訊
nReturnValue = unzGetCurrentFileInfo(unzfile, pFileInfo, szZipFName, MAX_PATH, szExtraName, MAX_PATH, szCommName, MAX_PATH);
if (nReturnValue != UNZ_OK)
return;
std::cout << "解壓檔名: " << szZipFName << endl;
string strZipFName = szZipFName;
// 如果是目錄則執行建立遞迴目錄名
if (pFileInfo->external_fa == FILE_ATTRIBUTE_DIRECTORY || (strZipFName.rfind('/') == strZipFName.length() - 1))
{
destFilePath = strTempPath + "//" + szZipFName;
CreateDirectoryA(destFilePath.c_str(), NULL);
}
// 如果是檔案則解壓縮並建立
else
{
// 建立檔案 儲存完整路徑
string strFullFilePath;
tempFilePath = strTempPath + "/" + szZipFName;
strFullFilePath = tempFilePath;
int nPos = tempFilePath.rfind("/");
int nPosRev = tempFilePath.rfind("\\");
if (nPosRev == string::npos && nPos == string::npos)
continue;
size_t nSplitPos = nPos > nPosRev ? nPos : nPosRev;
destFilePath = tempFilePath.substr(0, nSplitPos + 1);
if (!PathIsDirectoryA(destFilePath.c_str()))
{
// 將路徑格式統一
destFilePath = replace_all(destFilePath, "/", "\\");
// 建立多級目錄
int bRet = CreatedMultipleDirectory(destFilePath);
}
strFullFilePath = replace_all(strFullFilePath, "/", "\\");
HANDLE hFile = CreateFileA(strFullFilePath.c_str(), GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return;
}
// 開啟檔案
nReturnValue = unzOpenCurrentFile(unzfile);
if (nReturnValue != UNZ_OK)
{
CloseHandle(hFile);
return;
}
// 讀取檔案
uLong BUFFER_SIZE = pFileInfo->uncompressed_size;;
void* szReadBuffer = NULL;
szReadBuffer = (char*)malloc(BUFFER_SIZE);
if (NULL == szReadBuffer)
{
break;
}
while (TRUE)
{
memset(szReadBuffer, 0, BUFFER_SIZE);
int nReadFileSize = 0;
nReadFileSize = unzReadCurrentFile(unzfile, szReadBuffer, BUFFER_SIZE);
// 讀取檔案失敗
if (nReadFileSize < 0)
{
unzCloseCurrentFile(unzfile);
CloseHandle(hFile);
return;
}
// 讀取檔案完畢
else if (nReadFileSize == 0)
{
unzCloseCurrentFile(unzfile);
CloseHandle(hFile);
break;
}
// 寫入讀取的內容
else
{
DWORD dWrite = 0;
BOOL bWriteSuccessed = WriteFile(hFile, szReadBuffer, BUFFER_SIZE, &dWrite, NULL);
if (!bWriteSuccessed)
{
unzCloseCurrentFile(unzfile);
CloseHandle(hFile);
return;
}
}
}
free(szReadBuffer);
}
unzGoToNextFile(unzfile);
}
delete pFileInfo;
delete pGlobalInfo;
// 關閉
if (unzfile)
{
unzClose(unzfile);
}
}
replace_all 函數
std::string& replace_all(std::string& str, const std::string& old_value, const std::string& new_value)
功能:在字串 str 中替換所有的 old_value 為 new_value。
引數:
返回值:替換後的字串。
CreatedMultipleDirectory 函數
BOOL CreatedMultipleDirectory(const std::string& direct)
功能:建立多級目錄,確保路徑存在。
引數:
UnzipFile 函數
void UnzipFile(const std::string& strFilePath, const std::string& strTempPath)
功能:遞迴解壓縮 ZIP 檔案。
引數:
該函數開啟 ZIP 檔案,獲取檔案資訊,然後逐個解析和處理 ZIP 檔案中的檔案或目錄。在解析過程中,根據檔案或目錄的屬性,建立相應的目錄結構,然後將檔案寫入目標路徑。
int main(int argc, char* argv[])
{
std::string srcFilePath = "D:\\lyshark\\test.zip";
std::string tempdir = "D:\\lyshark\\test";
// 如果傳入目錄不存在則建立
if (!::PathFileExistsA(tempdir.c_str()))
{
CreatedMultipleDirectory(tempdir);
}
// 呼叫解壓函數
UnzipFile(srcFilePath, tempdir);
system("pause");
return 0;
}
案例中,首先在解壓縮之前判斷傳入目錄是否存在,如果不存在則需要呼叫API建立目錄,如果存在則直接呼叫UnzipFIle
解壓縮函數,實現解包,輸出效果圖如下;