文件打包学习1

只愿长相守 提交于 2019-12-05 11:49:03

打包的定义

  什么是打包?打包这个词很形象,就是把零碎的文件进行统一重封装,统一管理,比如我们常见的RAR文件,ZIP文件都是很常见的包裹格式

打包的意义

  1. 比如RAR包,我们虽然能有工具解压,但是我们却基本上没有相关的SDK来做二次开发
  2. ZIP包虽然有SDK来读取,但是对于通用的文件格式,我们无法做到保护资源的需求
  3. 如果只是为了文件管理的方便,无所谓别人解开资源的话,直接用现成的ZIP开发的SDK即可

打包的方式

  • 分类打包
    比如图片资源打一个包,声音资源打一个包
  • 全部打包
    把所有资源一起打包

打包的一般准则和规范

  1. 原始文件的标识,这个标识可以使原始文件名+路径名,或者也可以是转换后的数据如ID等,先从最简单的说起,使用原始文件名+路径名
  2. 原始文件的大小,把文件打进包裹之后,我们要知道这个原始文件有多大
  3. 原始文件的数据打包在包裹的什么位置

打包程序的实现框架

  1. 包裹文件的定义
  2. 添加CreatPackage(创建空白包裹)函数、AddFileToPackage(添加一个文件到包裹)函数
  3. CreatePackage(创建空白包裹)函数的实现
  4. AddFileToPackage(添加一个文件到包裹)函数的实现

package.h

#pragma once

#include <cstdio>
#include <vector>

/*
* 根据分析得知,我们需要将多个文件打包一个文件中,这样我们这个文件相对于整个打包的文件的一些信息
* 当前只定义部分信息
*/

struct PackageItem
{
    char fileName[256];         //文件名
    size_t filesize;            //文件的大小
    size_t pos;                 //文件在整个打包文件的偏移位置
};

/*
* 定义一个打包文件
* 首先,打包文件最终依旧是一个文件,在这个类中,我们希望一个对象维护一个打包文件,
* 这样就需要这个对象持有一个文件指针,又因为一个打包文件不止一个文件,这样就需要用一个容器来存放多个文件
*/


class Package
{
public:
    Package() :m_fpPackage(nullptr)
    {
    }
    ~Package();

public:
    //创建一个包裹
    bool CreatePackage(const char* szFileName);
    //向一个包裹中添加一个文件
    bool AddFileToPackage(const char* szFileName);

private:
    FILE* m_fpPackage;
    std::vector<PackageItem> m_packageItems;
};

package.cpp

#include "stdafx.h"
#include "Package.h"

Package::~Package()
{
    fclose(m_fpPackage);
    m_fpPackage = nullptr;
}

bool Package::CreatePackage(const char *szFileName)
{
    //首先判断包裹是否已经被打开,如果已经打开就直接返回
    if (m_fpPackage)
        return false;
    
    //判断文件名
    if (!szFileName)
        return false;

    //到这里,说明当前对象还没有创建文件,开始创建文件,由于打包的文件是二进制的,这个地方使用"wb"的方式打开
    m_fpPackage = fopen(szFileName, "wb");
    if (!m_fpPackage)
    {
        fprintf(stderr, "fopen %s error : %s\n", szFileName, strerror(errno));
        return false;
    }

    return true;
}

bool Package::AddFileToPackage(const char * szFileName)
{
    //由于这个地方要添加一个文件到包裹中,先以只读的方式打开文件"rb"
    FILE* fp = fopen(szFileName,"rb");
    if (!fp)
    {
        fprintf(stderr, "fopen %s error : %s\n", szFileName, strerror(errno));
        return false;
    }

    //获取要添加的文件大小
    fseek(fp, 0, SEEK_END);
    size_t szFileSize = ftell(fp);
    
    //由于我们将文件指针移动到了文件的末尾,此时文件被没有写入到包裹中,需要再将文件指针移动到文件的头
    rewind(fp);
    //fseek(fp, 0, SEEK_SET);

    //定义一个文件对象,把文件的信息放进去
    //由于PackageItem是没有虚函数的类,可以使用memset,如果有虚函数,不能使用memset,会影响虚函数表函数指针的挂接
    //关于文件相对于整个包裹的位置,只需要通过获取包裹的文件指针位置就可以获取到
    PackageItem item;
    memset(&item, 0, sizeof(item));
    strncpy(item.fileName, szFileName, strlen(szFileName));
    item.filesize = szFileSize;
    item.pos = ftell(m_fpPackage);

    //将文件信息写入包裹中
    size_t nWrite;
    if ((nWrite = fwrite(&item, 1, sizeof(item), m_fpPackage)) != sizeof(item))
    {
        fprintf(stderr, "文件头部 %s 信息写入失败!\n", szFileName);
        return false;
    }

    //写入文件信息
    //由于是按照指定的大小写入文件,需要循环写入文件
    size_t nRead;
    char buff[65536];
    while (true)
    {
        nRead = fread(buff, 1, sizeof(buff), fp);
        fwrite(buff, 1, nRead, m_fpPackage);
        if (nRead < sizeof(buff))
            break;
    }

    //由于我们使用的是文件流,文件流在内存中也是一份缓存,需要将缓存写入到硬件中
    fflush(m_fpPackage);

    fclose(fp);

    return true;
}

解包程序的实现框架

  1. 新建一个CPackageLoader类(这个类负责解出Package1打包的文件)
  2. 在CPackageLaoder类中添加几个接口
  3. 实现打开包裹文件的OpenPackage函数
  4. 实现得到包裹里面有多少个PackageItemq结构的GetPackageItemCount函数
  5. 实现得到打包文件的信息的GetPackageItem函数
  6. 实现导出包裹文件并保存到szTargetName的ExportPackageItem函数
  7. 测试Package2解包程序

PackageLoader.h

#pragma once

#include <vector>
#include <cstdio>

struct PackageItem
{
    char fileName[256];         //文件名
    size_t filesize;            //文件的大小
    size_t pos;                 //文件在整个打包文件的偏移位置
};

class PackageLoader
{
public:
    PackageLoader() :m_fpPackage(nullptr)
    {

    }
    ~PackageLoader()
    {
        fclose(m_fpPackage);
        m_fpPackage = nullptr;
    }
public:
    //打开一个包裹
    bool OpenPackage(const char* szFileName);
    //获取一个包裹中的文件数
    const size_t GetPackageItemCount()const;
    const PackageItem* GetPackageItems(size_t index)const;
    //导出包裹中的文件
    bool ExportPackageItems(const PackageItem *p, const char* szFileName);
private:
    FILE* m_fpPackage;
    std::vector<PackageItem> m_packageItems;
};

PackageLoader.cpp

#include "stdafx.h"
#include "PackageLoader.h"

bool PackageLoader::OpenPackage(const char * szFileName)
{
    if (m_fpPackage)
        return false;

    m_fpPackage = fopen(szFileName, "rb");
    if (!m_fpPackage)
    {
        fprintf(stderr, "fopen %s error %s\n", szFileName, strerror(errno));
        return false;
    }
    
    while (1)
    {
        PackageItem item;
        memset(&item, 0, sizeof(PackageItem));
        if (fread(&item, 1, sizeof(PackageItem), m_fpPackage) != sizeof(PackageItem))
        {
            break;
        }
        m_packageItems.push_back(item);
        fseek(m_fpPackage, item.filesize, SEEK_CUR);
    }

    return true;
}

const size_t PackageLoader::GetPackageItemCount()const
{
    return m_packageItems.size();
}

const PackageItem * PackageLoader::GetPackageItems(size_t index)const
{
    if (index < 0 && index >= m_packageItems.size())
        return nullptr;
    return &m_packageItems[index];
}

bool PackageLoader::ExportPackageItems(const PackageItem * pItem, const char * szFileName)
{
    FILE* fp = fopen(szFileName, "wb");
    if (!fp)
    {
        fprintf(stderr, "fopen %s error %s\n", szFileName, strerror(errno));
        return false;
    }

    fseek(m_fpPackage, pItem->pos + sizeof(*pItem), SEEK_SET);

    
    char szBuffer[65536];
    int leftSize = pItem->filesize;
    while (1)
    {
        int readSize = leftSize;
        if (readSize > sizeof(szBuffer))
        {
            readSize = sizeof(szBuffer);
        }
        int nReadBytes = fread(szBuffer, 1, readSize, m_fpPackage);
        assert(nReadBytes == readSize);
        fwrite(szBuffer, 1, nReadBytes, fp);

        leftSize -= nReadBytes;
        if (leftSize == 0)
        {
            break;
        }
    }

    fclose(fp);
    return true;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!