1. 程式人生 > >心得 ~ 使用 zlib庫 解壓縮 zip檔案

心得 ~ 使用 zlib庫 解壓縮 zip檔案

最近在完成一個專案,需要用到C++語言讀取一個zip檔案內指定檔案的內容。在網上查閱了不少資料,針對過程中遇到的問題,自己也研究了一下,現將方法心得記錄下來。

關於解壓檔案的方法,根據網上的資料,大概有以下三種方法:

  1. 呼叫rar.exe等外部程式。
  2. 使用第三方類庫。
  3. 自己寫解壓方法。

第一種方法,個人感覺不太靠譜,捨去。第三種方法,本人對zip壓縮演算法一頭霧水,加上暫時沒必要做此類研究,捨去。直接拿來主義,用第二種。

在此需要說明一點,如果你用的是Visual Studio,只需要下載zlib原始碼即可,完了需要自己執行生成動態庫檔案;如果不想太麻煩,或者只有VC++6.0,需要將動態庫檔案也下載下來。我機子上只有VC++6.0,因此直接把原始碼和動態庫檔案都下載了,如果你想自己編譯生成,可以參考

http://blog.sina.com.cn/s/blog_659b2b3201013y9k.html

下載完成後,就可以使用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為例。

  1. 開啟 project -> settings,選擇 C/C++ -> General,在 Preprocessor definitions 中,加入 ZLIB_WINAPI;
  2. 接著選擇 Link -> Input,在 Object/library modules 和 Additional library path 中加入 zlib/zlibwapi.lib;
  3. 儲存。

這樣在使用的時候,只需要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庫壓縮檔案。如果朋友們對這方面有興趣,可以在網上搜索相關資料,自己研究下,還是比較簡單的。

如果文章中有什麼錯誤的地方,歡迎大家指正。