最近在完成一个项目,需要用到C++语言读取一个zip文件内指定文件的内容。在网上查阅了不少资料,针对过程中遇到的问题,自己也研究了一下,现将方法心得记录下来。
关于解压文件的方法,根据网上的资料,大概有以下三种方法:
- 调用rar.exe等外部程序。
- 使用第三方类库。
- 自己写解压方法。
第一种方法,个人感觉不太靠谱,舍去。第三种方法,本人对zip压缩算法一头雾水,加上暂时没必要做此类研究,舍去。直接拿来主义,用第二种。
第三方类库选用有名的zlib,官方网址为http://www.zlib.net/,我是从http://www.winimage.com/zLibDll/index.html下载的,1.2.5版。
在此需要说明一点,如果你用的是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为例。
-
打开 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库压缩文件。如果朋友们对这方面有兴趣,可以在网上搜索相关资料,自己研究下,还是比较简单的。
如果文章中有什么错误的地方,欢迎大家指正。