C/C++ Zlib庫封裝MyZip壓縮類

微軟技術分享發表於2023-11-29

Zlib是一個開源的資料壓縮庫,提供了一種通用的資料壓縮和解壓縮演算法。它最初由Jean-Loup Gailly和Mark Adler開發,旨在成為一個高效、輕量級的壓縮庫,其被廣泛應用於許多領域,包括網路通訊、檔案壓縮、資料庫系統等。其壓縮演算法是基於DEFLATE演算法,這是一種無損資料壓縮演算法,通常能夠提供相當高的壓縮比。

在軟體開發中,檔案的壓縮和解壓縮是一項常見的任務,而ZIP是一種被廣泛應用的壓縮格式。為了方便地處理ZIP壓縮和解壓縮操作,開發者通常使用各種程式語言和庫來實現這些功能。本文將聚焦於一個簡化的C++實現,透過分析程式碼,我們將深入瞭解其設計和實現細節。

類的功能實現

MyZip類旨在提供簡單易用的ZIP壓縮和解壓縮功能。透過成員函式CompressUnCompress,該類使得對目錄的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;
}

輸出效果如下所示;

相關文章