心得 ~ 使用 zlib庫 解壓縮 zip檔案
最近在完成一個專案,需要用到C++語言讀取一個zip檔案內指定檔案的內容。在網上查閱了不少資料,針對過程中遇到的問題,自己也研究了一下,現將方法心得記錄下來。
關於解壓檔案的方法,根據網上的資料,大概有以下三種方法:
- 呼叫rar.exe等外部程式。
- 使用第三方類庫。
- 自己寫解壓方法。
第一種方法,個人感覺不太靠譜,捨去。第三種方法,本人對zip壓縮演算法一頭霧水,加上暫時沒必要做此類研究,捨去。直接拿來主義,用第二種。
在此需要說明一點,如果你用的是Visual Studio,只需要下載zlib原始碼即可,完了需要自己執行生成動態庫檔案;如果不想太麻煩,或者只有VC++6.0,需要將動態庫檔案也下載下來。我機子上只有VC++6.0,因此直接把原始碼和動態庫檔案都下載了,如果你想自己編譯生成,可以參考
下載完成後,就可以使用zlib庫了。
zlib庫使用前的配置
zlib庫在使用的時候,不是簡單地直接include就可以了,還需要一些配置。
由於我只是使用簡單的解壓功能,所以只需要以下檔案即可:
zlib-1.2.5\zlib.h
zlib-1.2.5\zconf.h
zlib-1.2.5\contrib\minizip\unzip.h
zlib-1.2.5\contrib\minizip\unzip.c
zlib-1.2.5\contrib\minizip\ioapi.h
zlib-1.2.5\contrib\minizip\ioapi.c
我在工程目錄中建了個zlib資料夾,將這些檔案拷貝進去。如果你在編譯的過程中遇到缺少標頭檔案的錯誤,根據錯誤資訊將對應的zlib庫檔案加入即可。
接下來,將動態庫檔案也拷貝進來,也就是zlibwapi.lib檔案和zlibwapi.dll檔案。
接著在需要用到zlib的地方,加入以下程式碼即可:
#define ZLIB_WINAPI
#include "zlib/unzip.h"
#pragma comment(lib, "zlib/zlibwapi.lib")
注意 ,預處理定義和動態庫是必須的,否則link的時候會出錯。我這裡也是研究了半天才搞定,不知道大家有沒有遇到這種問題。
在這裡還有另一種方法,載入預處理和動態庫。以VC++6.0為例。
- 開啟 project -> settings,選擇 C/C++ -> General,在 Preprocessor definitions 中,加入 ZLIB_WINAPI;
- 接著選擇 Link -> Input,在 Object/library modules 和 Additional library path 中加入 zlib/zlibwapi.lib;
- 儲存。
這樣在使用的時候,只需要include標頭檔案即可。
此外,如果你在執行程式的時候遇到以下錯誤:
無法啟動此程式,因為計算機中丟失 zlibwapi.dll。嘗試重新安裝該程式以解決此問題。
只需要將 zlibwapi.dll 檔案,拷貝到系統目錄下的 Windows\system 目錄下即可。
使用zlib解壓檔案
對於zlib的函式定義,具體使用方法等,網上資料很多。zlib的原始碼中也提供了使用範例,有興趣的朋友可以研究一下。我這裡只介紹自己用到的相關方法。
以下是開啟zip檔案的方法:
// zip檔案路徑
char FILEPATH[] = "aa.zip";
unzFile zFile;
zFile = unzOpen64(FILEPATH);
if (zFile == NULL)
{
cout << FILEPATH << "檔案開啟失敗" << endl;
return -1;
}
如果 zFile 不等於空,就表示檔案開啟成功,可以繼續接下來的工作。
首先來獲取壓縮檔案的全域性資訊:
unz_global_info64 zGlobalInfo;
if (UNZ_OK != unzGetGlobalInfo64(zFile, &zGlobalInfo))
{
// 錯誤處理
cout << __FILE__ << "中" << __LINE__ << "行錯誤;得到全域性資訊出錯!" << endl;
return -1;
}
unz_global_info64 是zlib庫中定義的結構體,裡邊最重要的成員變數就是壓縮檔案內所有檔案的數量。注意這個檔案的數量並不包括目錄。
下面迴圈遍歷所有檔案:
unz_file_info64 zFileInfo;
unsigned int num = 512;
char *fileName = new char[num];
for (int i = 0; i < zGlobalInfo.number_entry; i++)
{
// 遍歷所有檔案
if (UNZ_OK != unzGetCurrentFileInfo64(zFile, &zFileInfo, fileName, num, NULL, 0, NULL, 0))
{
//錯誤處理資訊
cout << __FILE__ << "中" << __LINE__ << "行錯誤;得到當前檔案資訊出錯!" << endl ;
}
unzGoToNextFile(zFile);
}
extern int ZEXPORT unzGetCurrentFileInfo64 OF((unzFile file,
unz_file_info64 *pfile_info,
char *szFileName,
uLong fileNameBufferSize,
void *extraField,
uLong extraFieldBufferSize,
char *szComment,
uLong commentBufferSize));
該函式的功能,是獲取壓縮包內當前讀取的檔案資訊。其中比較重要的作用是獲取當前讀取的檔名,通過 szFileName 引數返回,這是一個char型別的陣列,而 fileNameBufferSize 的值,就是返回的檔名的長度。也就是說,如果當前檔名是 "abcdefg.txt",而 fileNameBufferSize 的值設為了4,則最後 szFileName 的值就是 "abcd"。還有一點需要說明的是,這裡返回的檔名,是包括目錄路徑在內的全路徑。
unz_file_info64 也是zlib庫中定義的結構體,儲存的是當前檔案的資訊,比較常用的成員變數有:
uLong compression_method 壓縮方法
uLong dosDate 最後修改日期
ZPOS64_T compressed_size 壓縮後的檔案大小,按位元組數計
ZPOS64_T uncompressed_size 原始檔案大小,解壓後的檔案大小,按位元組數計
uLong size_filename 檔名長度
獲取了當前檔案的資訊,接下來就可以解壓獲取檔案內容了:
if (UNZ_OK != unzOpenCurrentFile(zFile))
{
//錯誤處理資訊
cout << __FILE__ << "中" << __LINE__ << "行錯誤;開啟壓縮包中" << fileName << "檔案失敗!" << endl ;
}
cout << "壓縮檔案" << fileName << "內容為:" << endl ;
int fileLength = zFileInfo.uncompressed_size;
char *fileData = new char[fileLength];
int len = 1 ;
while (len)
{
//解壓縮檔案
len = unzReadCurrentFile(zFile, (voidp)fileData, fileLength - 1);
fileData[len] = '\0';
for (int j = 0; j < len; j++)
{
file.push_back(fileData[j]);
}
}
for (int j = 0; j < file.size(); j++)
{
cout << file[j];
}
unzCloseCurrentFile(zFile);
free(fileData);
首先通過函式 unzOpenCurrentFile 開啟當前檔案,然後通過函式 unzReadCurrentFile 讀取當前檔案資訊,相關處理完成後需要關閉當前檔案。
extern int ZEXPORT unzReadCurrentFile OF((unzFile file,
voidp buf,
unsigned len));
該函式的意思,是按照 len 的長度,按位元組讀取當前檔案的內容,儲存到 buf 中,並返回實際讀到的位元組數。也就是說,如果當前檔案大小為95個位元組,給定 len = 10,則每次只會讀取10個位元組的資料儲存到 buf 中,每次呼叫函式返回10。而最後一次讀取,雖然也是讀取10個位元組的資料,但檔案只剩下5個位元組資料還未讀到,那麼最終的返回結果就成了5。
因此在實際讀取檔案的時候,可以按照當前檔案資訊內的 zFileInfo.uncompressed_size 變數的值,一次性讀取全部檔案內容;或者按照不同需求,讀取不同長度的內容。
如果在當前zip檔案中,只需要某一個檔案的內容,而又知道具體檔名的話,就可以不通過迴圈,直接定位到所需檔案。
char *fileName = "aa.txt";
if (UNZ_OK == unzLocateFile(zFile, fileName, 0))
{
// 檔案處理
}
需要注意的是,此處傳入的檔名,必須是包括目錄路徑在內的全路徑,否則搜尋不到。
extern int ZEXPORT unzLocateFile OF((unzFile file,
const char *szFileName,
int iCaseSensitivity));
該函式中 iCaseSensitivity 引數表示檔案匹配方式,1表示區分大小寫,2表示不區分大小寫,0表示按照作業系統確定,linux下區分,windows下不區分。
最後,需要呼叫
unzClose(zFile)
關閉開啟的zip檔案。同時不要忘了釋放相關變數的記憶體。
後記
這篇文章只是記錄了自己專案中用到的zlib使用方法,並沒有做更深入的研究。而且由於專案需求原因,我暫時沒有研究如何通過zlib庫壓縮檔案。如果朋友們對這方面有興趣,可以在網上搜索相關資料,自己研究下,還是比較簡單的。
如果文章中有什麼錯誤的地方,歡迎大家指正。