Zlib是一個開源的資料壓縮庫,提供了一種通用的資料壓縮和解壓縮演算法。它最初由Jean-Loup Gailly和Mark Adler開發,旨在成為一個高效、輕量級的壓縮庫,其被廣泛應用於許多領域,包括網路通訊、檔案壓縮、資料庫系統等。其壓縮演算法是基於DEFLATE演算法,這是一種無損資料壓縮演算法,通常能夠提供相當高的壓縮比。
在軟體開發中,檔案的壓縮和解壓縮是一項常見的任務,而ZIP是一種被廣泛應用的壓縮格式。為了方便地處理ZIP壓縮和解壓縮操作,開發者通常使用各種程式語言和庫來實現這些功能。本文將聚焦於一個簡化的C++實現,透過分析程式碼,我們將深入瞭解其設計和實現細節。
類的功能實現
MyZip
類旨在提供簡單易用的ZIP壓縮和解壓縮功能。透過成員函式Compress
和UnCompress
,該類使得對目錄的ZIP壓縮和ZIP檔案的解壓變得相對容易。
ZIP壓縮函式 Compress
Compress
函式透過zlib庫提供的ZIP壓縮功能,遞迴地將目錄下的檔案新增到ZIP檔案中。其中,nyCollectfileInDirtoZip
函式負責遍歷目錄,而nyAddfiletoZip
函式則用於新增檔案到ZIP中。這種設計使得程式碼模組化,易於理解。
ZIP解壓函式 UnCompress
UnCompress
函式透過zlib庫提供的ZIP解壓功能,將ZIP檔案解壓到指定目錄。函式中使用了unz
系列函式來遍歷ZIP檔案中的檔案資訊,並根據檔案型別進行相應的處理。這包括建立目錄和寫入檔案,使得解壓後的目錄結構與ZIP檔案一致。
將如上的壓縮與解壓方法封裝成MyZip
類,呼叫zip.Compress()
實現壓縮目錄,呼叫zip.UnCompress()
則實現解壓縮目錄。這些函式使用了zlib庫的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")
class MyZip
{
private:
// 向ZIP檔案中新增檔案
bool nyAddfiletoZip(zipFile zfile, const std::string& fileNameinZip, const std::string& srcfile)
{
if (NULL == zfile || fileNameinZip.empty())
{
return false;
}
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, "\\");
}
// 在ZIP中開啟新檔案
nErr = zipOpenNewFileInZip(zfile, sznewfileName, &zinfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION);
if (nErr != ZIP_OK)
{
return false;
}
// 如果有原始檔,讀取並寫入ZIP檔案
if (!srcfile.empty())
{
FILE* srcfp = _fsopen(srcfile.c_str(), "rb", _SH_DENYNO);
if (NULL == srcfp)
{
return false;
}
int numBytes = 0;
char* pBuf = new char[1024 * 100];
if (NULL == pBuf)
{
return false;
}
// 逐塊讀取原始檔並寫入ZIP
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);
}
// 關閉ZIP檔案中的當前檔案
zipCloseFileInZip(zfile);
return true;
}
// 遞迴地將目錄下的檔案新增到ZIP
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
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);
}
// 將檔案新增到ZIP
nyAddfiletoZip(zfile, relativepath, szTemp);
} while (::FindNextFileA(hFile, &findFileData));
FindClose(hFile);
return true;
}
// 替換字串中的所有指定子串
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;
}
public:
// 壓縮目錄
bool Compress(const std::string& dirpathName, const std::string& zipfileName, const std::string& parentdirName)
{
bool bRet = false;
zipFile zFile = NULL;
// 根據ZIP檔案是否存在選擇開啟方式
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)
{
return bRet;
}
// 將目錄下的檔案新增到ZIP
if (nyCollectfileInDirtoZip(zFile, dirpathName, parentdirName))
{
bRet = true;
}
zipClose(zFile, NULL);
return bRet;
}
// 解壓目錄
bool UnCompress(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 false;
}
unz_global_info* pGlobalInfo = new unz_global_info;
nReturnValue = unzGetGlobalInfo(unzfile, pGlobalInfo);
if (nReturnValue != UNZ_OK)
{
return false;
}
unz_file_info* pFileInfo = new unz_file_info;
char szZipFName[MAX_PATH] = { 0 };
char szExtraName[MAX_PATH] = { 0 };
char szCommName[MAX_PATH] = { 0 };
for (int i = 0; i < pGlobalInfo->number_entry; i++)
{
nReturnValue = unzGetCurrentFileInfo(unzfile, pFileInfo, szZipFName, MAX_PATH, szExtraName, MAX_PATH, szCommName, MAX_PATH);
if (nReturnValue != UNZ_OK)
return false;
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 false;
}
nReturnValue = unzOpenCurrentFile(unzfile);
if (nReturnValue != UNZ_OK)
{
CloseHandle(hFile);
return false;
}
uLong BUFFER_SIZE = pFileInfo->uncompressed_size;
void* szReadBuffer = NULL;
szReadBuffer = (char*)malloc(BUFFER_SIZE);
if (NULL == szReadBuffer)
{
break;
}
// 逐塊讀取ZIP檔案並寫入目標檔案
while (TRUE)
{
memset(szReadBuffer, 0, BUFFER_SIZE);
int nReadFileSize = 0;
nReadFileSize = unzReadCurrentFile(unzfile, szReadBuffer, BUFFER_SIZE);
if (nReadFileSize < 0)
{
unzCloseCurrentFile(unzfile);
CloseHandle(hFile);
return false;
}
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 false;
}
}
}
free(szReadBuffer);
}
unzGoToNextFile(unzfile);
}
delete pFileInfo;
delete pGlobalInfo;
if (unzfile)
{
unzClose(unzfile);
}
return true;
}
};
如何使用類
壓縮檔案時可以透過呼叫zip.Compress()
函式實現,該函式接受3個引數,第一個引數是需要壓縮的目錄名,第二個引數是壓縮後儲存的檔名,第三個引數則是壓縮後主目錄的名字,我們以壓縮D:\\csdn
目錄下的所有檔案為例,程式碼如下所示;
int main(int argc, char* argv[])
{
MyZip zip;
// 壓縮目錄
std::string compress_src = "D:\\csdn"; // 壓縮目錄
std::string compress_dst = "D:\\test.zip"; // 壓縮後
bool compress_flag = zip.Compress(compress_src, compress_dst, "lyshark");
std::cout << "壓縮狀態: " << compress_flag << std::endl;
system("pause");
return 0;
}
壓縮後可以看到對應的壓縮包內容,如下所示;
解壓縮與壓縮類似,透過呼叫zip.UnCompress
實現,該方法需要傳入兩個引數,被壓縮的檔名和解壓到的目錄名,如果目錄不存在則會建立並解壓。
int main(int argc, char* argv[])
{
MyZip zip;
// 解壓縮目錄
std::string uncompress_src = "D:\\test.zip"; // 被解壓檔案
std::string uncompress_dst = "D:\\dst"; // 解壓到
bool compress_flag = zip.UnCompress(uncompress_src, uncompress_dst);
std::cout << "解壓縮狀態: " << compress_flag << std::endl;
system("pause");
return 0;
}
輸出效果如下所示;