C++ 基于ZLIB压缩库的数据或文件的压缩与解压缩小程序

Posted 流楚丶格念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 基于ZLIB压缩库的数据或文件的压缩与解压缩小程序相关的知识,希望对你有一定的参考价值。

项目展示


项目开发

编译设置

1. Debug/Release编译模式下的编译设置

首先,打开项目工程的属性页,然后将SDK和“平台工具集”选为“你当前的SDK和平台工具集”。

例如我是 SDK 10 平台工具集 v141。

接着,展开“C/C++”,点击“预处理器”,然后在“预处理器定义”中添加“ZLIB_WINAPI”,否则,代码不能编译过去。

接着点击“代码生成”,在“运行库”中设置为“/MTd”选项,表示Debug模式下的多线程静态编译。

接着,展开“链接器”,点击“命令行”,在“其他选项(D)”编辑框中添加链接命令“/FORCE:MULTIPLE” ,这个选项告诉链接器去创建一个有效的exe文件或dll文件,即使一个函数或变量被多次引用或多出定义。

这样,Debug模式下的编译设置就完成了。

设计思路

1. 数据或文件的压缩思路

对于数据或文件的压缩,主要是使用ZLIB库提供的 compress 压缩函数。

int compress(Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen);

compress函数将source缓冲区中的内容压缩到dest缓冲区。

sourceLen表示source缓冲区的大小(以字节计)。

destLen是传址调用,当调用函数时,destLen表示dest缓冲区大小(初始值不能为0);当函数退出后,destLen表示压缩后缓冲区的实际大小。

返回值:
-5 : 输出缓冲区不够大;
-4 : 没有足够的内存;
0 : 表示成功;

所以,对于文件的压缩,我们根据文件路径,打开文件并读取文件全部数据,然后调用compress函数进行压缩。

其中,需要注意的一个问题便是,对于上述的目的缓冲区的大小难以确定,不能笼统地认为直接使用压缩前文件大小作为压缩后的缓冲区大小。因为,有些小文件,压缩过后,数据可能反而会变大。所以,设计了一个循环,去处理这种情况:

do
{
	iRet = compress(pDestData, &dwDestDataSize, pSrcData, dwFileSize);
	if (0 == iRet)
	{
		// 成功
		break;
	}
	else if (-5 == iRet)
	{
		// 输出缓冲区不够大, 以 100KB 大小递增
		delete[]pDestData;
		pDestData = NULL;
		dwDestDataSize = dwDestDataSize + (100 * 1024);
		pDestData = new BYTE[dwDestDataSize];
		if (NULL == pDestData)
		{
			delete[]pSrcData;
			pSrcData = NULL;
			::CloseHandle(hFile);
			return FALSE;
		}
	}
	else
	{
		// 没有足够的内存 或 其他情况
		delete[]pDestData;
		pDestData = NULL;
		delete[]pSrcData;
		pSrcData = NULL;
		::CloseHandle(hFile);
		return FALSE;
	}
} while (TRUE);

我们获取compress函数的返回码,判断成功还是出错。

若出错,则判断返回的出错类型,若是返回码为 -5 ,则表示输出缓冲区不够大,这时,便重新申请更大的目的缓冲区,继续调用compress函数压缩数据,继续获取操作返回码。这样,便可解决目的缓冲区大小不明确的问题。

2. 数据或文件的解压缩思路

对于数据或文件的压缩,主要是使用ZLIB库提供的 uncompress 解压缩函数。

int uncompress(Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen);

uncompress函数将source缓冲区中的内容压缩到dest缓冲区。

sourceLen表示source缓冲区的大小(以字节计)。

destLen是传址调用,当调用函数时,destLen表示dest缓冲区大小(初始值不能为0);当函数退出后,destLen表示压缩后缓冲区的实际大小。

返回值:
-5 : 输出缓冲区不够大;
-4 : 没有足够的内存;
0 : 表示成功;

所以,对于文件的解压缩,我们根据文件路径,打开文件并读取文件全部数据,然后调用uncompress函数进行解压缩。

针对输出缓冲区大小不明确的问题,我们解决思路也是按照上面数据压缩的解决思路来操作的,同样通过判断uncompress函数操作的返回码,来进行下一步操作。

编码实现

首先将zlib库相关文件放在源目录下

1. 导入ZLIB库文件

//*************************************************
//         zlib压缩库的头文件和静态库
//*************************************************
# include "zlib\\\\zconf.h"
# include "zlib\\\\zlib.h"
# ifdef _DEBUG
	#ifdef _WIN64
		#pragma comment(lib, "zlib\\\\x64\\\\debug\\\\zlibstat.lib")
	#else
		#pragma comment(lib, "zlib\\\\x86\\\\debug\\\\zlibstat.lib")
	#endif
# else
	#ifdef _WIN64
		#pragma comment(lib, "zlib\\\\x64\\\\release\\\\zlibstat.lib")
	#else
		#pragma comment(lib, "zlib\\\\x86\\\\release\\\\zlibstat.lib")
	#endif
# endif
//*************************************************

2. 文件压缩

// 数据压缩
// 输入:将要压缩文件的路径
// 输出:数据压缩后的压缩数据内容、数据压缩后的压缩数据内容长度
BOOL Zlib_CompressData(char *pszCompressFileName, BYTE **ppCompressData, DWORD *pdwCompressDataSize)
{
	// 注意可能出现压缩后的文件比压缩前的文件大的现象!!!

	// 打开文件 并 获取文件数据
	HANDLE hFile = ::CreateFile(pszCompressFileName, GENERIC_READ,
		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
		FILE_ATTRIBUTE_ARCHIVE, NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		Zlib_ShowError("CreateFile");
		return FALSE;
	}
	// 获取文件大小
	DWORD dwFileSize = ::GetFileSize(hFile, NULL);       
	if (MAX_SRC_FILE_SIZE < dwFileSize)
	{
		::CloseHandle(hFile);
		return FALSE;
	}
	// 判断是否满足大小限制条件
	if (MAX_SRC_FILE_SIZE < dwFileSize)
	{
		::CloseHandle(hFile);
		return FALSE;
	}
	DWORD dwDestDataSize = dwFileSize;

	BYTE *pSrcData = new BYTE[dwFileSize];
	if (NULL == pSrcData)
	{
		::CloseHandle(hFile);
		return FALSE;
	}
	BYTE *pDestData = new BYTE[dwDestDataSize];
	if (NULL == pDestData)
	{
		::CloseHandle(hFile);
		return FALSE;
	}
	// 读取文件数据
	DWORD dwRet = 0;
	::ReadFile(hFile, pSrcData, dwFileSize, &dwRet, NULL);	 
	if ((0 >= dwRet) ||
		(dwRet != dwFileSize))
	{
		delete[]pDestData;
		pDestData = NULL;
		delete[]pSrcData;
		pSrcData = NULL;
		::CloseHandle(hFile);
		return FALSE;
	}

	// 压缩数据
	int iRet = 0;
	do
	{
		iRet = compress(pDestData, &dwDestDataSize, pSrcData, dwFileSize);
		if (0 == iRet)
		{
			// 成功
			break;
		}
		else if (-5 == iRet)
		{
			// 输出缓冲区不够大, 以 100KB 大小递增
			delete[]pDestData;
			pDestData = NULL;
			dwDestDataSize = dwDestDataSize + (100 * 1024);
			pDestData = new BYTE[dwDestDataSize];
			if (NULL == pDestData)
			{
				delete[]pSrcData;
				pSrcData = NULL;
				::CloseHandle(hFile);
				return FALSE;
			}
		}
		else
		{
			// 没有足够的内存 或 其他情况
			delete[]pDestData;
			pDestData = NULL;
			delete[]pSrcData;
			pSrcData = NULL;
			::CloseHandle(hFile);
			return FALSE;
		}
	} while (TRUE);
	// 返回数据
	*ppCompressData = pDestData;
	*pdwCompressDataSize = dwDestDataSize;

	delete[]pSrcData;
	pSrcData = NULL;
	::CloseHandle(hFile);

	return TRUE;
}

3. 文件解压缩

// 数据解压
// 输入:将要解压缩文件的路径
// 输出:数据解压后的数据内容、数据解压后的内容长度
BOOL Zlib_UncompressData(char *pszUncompressFileName, BYTE **ppUncompressData, DWORD *pdwUncompressDataSize)
{
	// 注意可能出现压缩后的文件比压缩前的文件大的现象!!!
	// 打开文件 并 获取文件数据
	HANDLE hFile = ::CreateFile(pszUncompressFileName, GENERIC_READ,
		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
		FILE_ATTRIBUTE_ARCHIVE, NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		Zlib_ShowError("CreateFile");
		return FALSE;
	}
	// 获取文件大小
	DWORD dwFileSize = ::GetFileSize(hFile, NULL);       
	DWORD dwDestDataSize = MAX_SRC_FILE_SIZE;

	BYTE *pSrcData = new BYTE[dwFileSize];
	if (NULL == pSrcData)
	{
		::CloseHandle(hFile);
		return FALSE;
	}
	BYTE *pDestData = new BYTE[dwDestDataSize];
	if (NULL == pDestData)
	{
		::CloseHandle(hFile);
		return FALSE;
	}
	// 读取文件数据
	DWORD dwRet = 0;
	::ReadFile(hFile, pSrcData, dwFileSize, &dwRet, NULL);	 
	if ((0 >= dwRet) ||
		(dwRet != dwFileSize))
	{
		delete[]pDestData;
		pDestData = NULL;
		delete[]pSrcData;
		pSrcData = NULL;
		::CloseHandle(hFile);
		return FALSE;
	}

	// 解压缩数据
	int iRet = 0;
	do
	{
		iRet = uncompress(pDestData, &dwDestDataSize, pSrcData, dwFileSize);
		if (0 == iRet)
		{
			// 成功
			break;
		}
		else if (-5 == iRet)
		{
			// 输出缓冲区不够大, 以 100KB 大小递增
			delete[]pDestData;
			pDestData = NULL;
			dwDestDataSize = dwDestDataSize + (100 * 1024);
			pDestData = new BYTE[dwDestDataSize];
			if (NULL == pDestData)
			{
				delete[]pSrcData;
				pSrcData = NULL;
				::CloseHandle(hFile);
				return FALSE;
			}
		}
		else
		{
			// 没有足够的内存 或 其他情况
			delete[]pDestData;
			pDestData = NULL;
			delete[]pSrcData;
			pSrcData = NULL;
			::CloseHandle(hFile);
			return FALSE;
		}
	} while (TRUE);
	// 返回数据
	*ppUncompressData = pDestData;
	*pdwUncompressDataSize = dwDestDataSize;
	
	delete[]pSrcData;
	pSrcData = NULL;
	::CloseHandle(hFile);

	return TRUE;
}

4. 将数据保存为文件

// 将数据存储为文件
// 输入:数据原文件路径、将要保存的数据内容、将要保存的数据内容长度
BOOL SaveToOriginalFile(char *pszFileName, BYTE *pData, DWORD dwDataSize)
{
	char szSaveName[MAX_PATH] = { 0 };
	::lstrcpy(szSaveName, pszFileName);
	::PathStripPath(szSaveName);
	// 创建文件
	HANDLE hFile = ::CreateFile(szSaveName, GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
		FILE_ATTRIBUTE_ARCHIVE, NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		Zlib_ShowError("CreateFile");
		return FALSE;
	}
	// 写入数据
	DWORD dwRet = 0;
	::WriteFile(hFile, pData, dwDataSize, &dwRet, NULL);
	// 关闭句柄
	::CloseHandle(hFile);

	return TRUE;
}

程序测试

我们在main函数中,调用上述封装好的函数,对文件进行压缩与解压缩的测试。main函数为:

int _tmain(int argc, _TCHAR* argv[])
{
	BOOL bRet = FALSE;
	BYTE *pCompressData = NULL;
	DWORD dwCompressDataSize = 0;
	BYTE *pUncompressData = NULL;
	DWORD dwUncompressDataSize = 0;

	// 压缩文件
	bRet = Zlib_CompressData("test.txt", &pCompressData, &dwCompressDataSize);
	if (FALSE == bRet)
	{
		return 1;
	}
	cout << "压缩成功!" << endl;

	// 保存压缩数据为文件
	bRet = SaveToOriginalFile("test.myzip", pCompressData, dwCompressDataSize);
	if (FALSE == bRet)
	{
		return 2;
	}
	cout << "保存压缩文件成功!" << endl;

	// 解压缩压缩文件
	bRet = Zlib_UncompressData("test.myzip", &pUncompressData, &dwUncompressDataSize);
	if (FALSE == bRet)
	{
		return 3;
	}
	cout << "解压缩成功!" << endl;

	// 保存压缩数据为文件
	bRet = SaveToOriginalFile("test_Uncompress.txt", pUncompressData, dwUncompressDataSize);
	if (FALSE == bRet)
	{
		return 4;
	}
	cout << "保存解压缩文件成功!" << endl;
	// 释放内存
	delete []pUncompressData;
	pUncompressData = NULL;
	delete []pCompressData;
	pCompressData = NULL;

	system("pause");
	return 0;
}

测试结果为:

可以看到,成功对大小为846KB的test.txt文件的数据进行压缩,得到大小为4KB的test.myzip文件。然后对test.myzip进行解压缩,得到和原来大小一样的test_Uncompress.txt文件。

项目链接

https://download.csdn.net/download/weixin_45525272/40317540

以上是关于C++ 基于ZLIB压缩库的数据或文件的压缩与解压缩小程序的主要内容,如果未能解决你的问题,请参考以下文章

Qt中用QuaZip来压缩和解压缩文件

iOS开发之压缩与解压文件

使用java对象Deflater对一个String类型压缩,在linux下使用c++调用zlib库inflate解压时为乱码

C/C++ 自制一个基于zlib的文件的(解)压缩系统

python zlib 压缩与解压

Ruby zlib 库解压 gzip 文件非常慢