通过打包将零碎的文件进行统一重封装,统一管理,比如我们常见的RAR文件,ZIP文件都是很常见的包裹格式 。本文将讲解一种简易的文件打包程序。
该示例程序并没有完全的压缩和安全保护机制,后期学习中再进一步完善。
一、打包技术的相关知识
1、打包的意义
实现对资源的管理以及安全性保护。常见的RAR包,虽然能有工具解压,但是我们却基本上没有相关的SDK来做二次开发。而ZIP包虽然有SDK来读取,但是对于通用的文件格式,我们无法做到保护资源的需求。所以要做到对资源的管理以及保护资源时,我们有必要自己实现打包。
2、常见的打包方式
分类打包:将各种资源分类打包,比如图片资源打一个包,声音资源打一个包
全部打包:把所有资源一起打包到一个文件中
3、打包的一般准则和规范
(1)原始文件的标识,这个标识可以使原始文件名+路径名,或者也可以是转换后的数据如ID等,先从最简单的说起,使用原始文件名+路径名
(2)原始文件的大小,把文件打进包裹之后,我们要知道这个原始文件有多大
(3)原始文件的数据打包在包裹的什么位置
二、打包示例程序讲解
1、CFileBuffer类
主要功能:进行对文件流的操作
#ifndef __FILEBUFFER_H__
#define __FILEBUFFER_H__
class CFileBuffer
{
public:
CFileBuffer() :m_pBuffer(NULL), m_nSize(0) {};
~CFileBuffer() { Destroy(); }
// 初始化申请文件流空间
bool Create(unsigned int Size);
// 清理工作
void Destroy();
// 获取文件流指针
void* GetBuffer() const { return m_pBuffer; }
// 获取文件流长度
unsigned int GetSize() const { return m_nSize; }
// 保存缓存中的文件到指定的硬盘文件中
bool Save(const char* szFile);
private:
void* m_pBuffer; // 文件流的指针
unsigned int m_nSize; // 文件流的长度
};
#endif // !__FILEBUFFER_H__
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
通过Create()初始化m_pBuffer所要使用的空间,然后将需要进行操作的文件写入m_pBuffer中,写入完成后,通过Save()将m_pBuffer中的数据保存到指定的文件名中。以此实现对文件的保存到硬盘的工作。
2、自定义包裹的内存结构
通过包裹的内存结构,我们接下来了解下进行打包的类的主要实现过程,务必牢记上图的结构。
3、PackageItem结构体
主要功能:记录每个包裹元素(在包裹中的每个文件为一个包裹元素)的文件名长度、文件大小、偏移量、文件名。因为是可变的文件名(节约内存),所以需要记录文件名的长度。
struct PackageItem
{
int FileNameLen; // 文件名的长度
int FileSize; // 假设文件不超过4G
unsigned int OffsetPackge; // 文件在包裹中的偏移量
char FileName[0]; // 可变的文件名
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
4、CPackage类
主要功能:实现将文件进行打包,解包的操作。
#ifndef __NEW_PACKAGE_H__
#define __NEW_PACKAGE_H__
#include "FileBuffer.h"
#include "PackageItem.h"
class CPackage
{
public:
CPackage();
~CPackage();
// 重置一个包裹
void ResetPackage();
// 读取一个包裹
bool OpenPackage(const char* szPackageName);
// 添加一个要被打包的文件,忽略大小写(实际是将要被打包的文件的文件名添加到m_AddFiles中)
bool AddFile(const char* szFileName);
// 删除一个文件,忽略大小写
bool RemoveFile(const char* szFileName);
// 重复性检查,忽略大小写
bool HasFile(const char* szFileName);
// 保存包裹到指定的文件名
bool SavePackage(const char* szPackageName);
// 导出文件数据到FileBuffer里面
bool ExportPackageItem(const char* szFileName, CFileBuffer & FileBuffer);
private:
FILE* m_fpPackage; // 包裹文件指针
std::vector<PackageItem*> m_PackageItem; // 已经打包到包裹的文件信息
std::vector<std::string> m_AddFiles; // 保存待添加到包裹的文件的文件名
};
#endif // !__NEW_PACKAGE_H__
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
关于打包函数SavePackage的具体实现讲解
bool CPackage::SavePackage(const char * szPackageName)
{
FILE* fpPackage = fopen(szPackageName, "wb");
if (!fpPackage)
{
printf("打开文件%s失败", szPackageName);
return false;
}
std::vector<FileItemForWrite> WriteItem; // 保存即将要写入到包裹中的每个元素信息
int TotalReadIndexSize = sizeof(int); // 初始化大小为‘包裹头用于保存元素个数的整型大小’
for (unsigned int i = 0; i < m_AddFiles.size(); i++)
{
FILE* fpRead = fopen(m_AddFiles[i].c_str(), "rb");
if (!fpRead)
{
printf("打开文件%s失败,已忽略\n", m_AddFiles[i].c_str());
continue;
}
fseek(fpRead, 0, SEEK_END);
FileItemForWrite Item;
Item.FileOffset = 0;
Item.fp = fpRead; // 此处我们保留的是指向文件末尾的指针
Item.FileSize = ftell(fpRead); // 获取要被打包的文件大小
Item.FileName = m_AddFiles[i];
int FileNamelen = Item.FileName.length() + 1;// 末尾还有一个'\0'
// 保存每个包元素(文件)信息
// 加上FileNameLen,这里使用的是可变长度的文件名,需要给FileName文件名申请足够空间
PackageItem* pNewItem = (PackageItem*)malloc(sizeof(PackageItem) + FileNamelen);
if(!pNewItem)
continue;
Item.pPackageItem = pNewItem;
pNewItem->FileNameLen = FileNamelen; // 文件名长度
pNewItem->FileSize = Item.FileSize; // 文件大小
strcpy(pNewItem->FileName, Item.FileName.c_str()); // 保存文件名
TotalReadIndexSize += sizeof(PackageItem) + pNewItem->FileNameLen;
WriteItem.push_back(Item);
}
for (unsigned int i = 0; i < m_PackageItem.size(); i++)
{
PackageItem* pItem = m_PackageItem[i];
FileItemForWrite Item;
Item.fp = m_fpPackage;
Item.FileOffset = pItem->OffsetPackge;
Item.FileSize = pItem->FileSize;
Item.FileName = pItem->FileName;
// 得到文件名长度,+1是包含了末尾的'\0'
int FileNamelen = Item.FileName.length() + 1;
// 分配PackageItem大小的空间,另外附加Item.FileName的长度
PackageItem* pNewItem = (PackageItem*)malloc(sizeof(PackageItem) + FileNamelen);
if (!pNewItem)
continue;
Item.pPackageItem = pNewItem;
pNewItem->FileNameLen = FileNamelen;
pNewItem->FileSize = Item.FileSize;
strcpy(pNewItem->FileName, Item.FileName.c_str());
TotalReadIndexSize += sizeof(PackageItem) + pNewItem->FileNameLen;
WriteItem.push_back(Item);
}
// 1、写入PackageItem的数量(4Bytes)
int TotalPackageItems = WriteItem.size();
if (sizeof(TotalPackageItems) != fwrite(&TotalPackageItems, 1, sizeof(TotalPackageItems), fpPackage))
{
printf("写入%s失败,打包失败", szPackageName);
return false;
}
// 2、跳过TotalReadIndexSize字节,首先写文件的内容,并记录写入索引的偏移位置
int IndexOffset = ftell(fpPackage);
// 预留TotalReadIndexSize的空间供索引写入,将文件的指针移动到TotalReadIndexSize个字节处
fseek(fpPackage, TotalReadIndexSize, SEEK_SET);
// 3、写入文件流的内容
bool bError = false;
int CurrentOffset = TotalReadIndexSize;
for (unsigned int i = 0; i < WriteItem.size(); i++)
{
FileItemForWrite & Item = WriteItem[i];
if (!bError)
{
// 返回填充数据偏移信息
Item.pPackageItem->OffsetPackge = CurrentOffset;
CurrentOffset += Item.FileSize;
fseek(Item.fp, Item.FileOffset, SEEK_SET);
// 写入文件数据,准备64k的缓冲区
char szBuffer[65535] = { 0 };
// 需要读取的文件大小
int LeftSize = Item.FileSize;
while (true)
{
// 实际要读取的大小
int ReadSize = LeftSize;
// 如果大于缓冲区,就只读取缓冲区大小的内容,剩余的循环再次读取
if (ReadSize > sizeof(szBuffer))
ReadSize = sizeof(szBuffer);
// 读取文件
int nReadBytes = fread(szBuffer, 1, ReadSize, Item.fp);
if (nReadBytes != nReadBytes)
{
printf("读取%s失败,打包失败\n", Item.FileName.c_str());
bError = true;
break;
}
// 写入包裹
if (nReadBytes != fwrite(szBuffer, 1, nReadBytes, fpPackage))
{
printf("写入%s失败,打包失败\n", szPackageName);
bError = true;
break;
}
LeftSize -= nReadBytes;
// 如果剩余大小为0,就读取完成了
if (LeftSize == 0)
break;
}
}
// 关闭打包的文件
if (Item.fp != m_fpPackage)
{
fclose(Item.fp);
Item.fp = NULL;
}
}
// 如果没有错误,写入索引
// 先将文件指针返回到PackageItem的起始位置
// 然后将没有PackageItem内容写入到文件中去
// 4、写PackageItem的内容
if (!bError)
{
fseek(fpPackage, IndexOffset, SEEK_SET);
for (unsigned int i = 0; i < WriteItem.size(); i++)
{
FileItemForWrite & Item = WriteItem[i];
PackageItem* pItem = Item.pPackageItem;
if (!bError)
{
int WriteSize = sizeof(PackageItem) + pItem->FileNameLen;
if (WriteSize != fwrite(pItem, 1, WriteSize, fpPackage))
{
printf("写入%s失败,打包失败\n", szPackageName);
bError = true;
}
}
free(pItem);
}
}
fclose(fpPackage);
return !bError;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
这里使用实现的简单流程图释义: