文件分割器实现时注意的几点
分割文件的头信息结构体的设计,如果里面需要存储字符串的信息,我们必须使用字符串数组来存储,而不能使用类似于CString、string等字符串对象来存储,因为使用这些对象在存储的时候还好,可以知道它的长度进行文件的写入,但是在读取的时候就会出现问题了,因为不知道读取多少。
解决方法有两个:
1. 在头文件信息结构体中加上构造函数和一些其它的辅助函数(这样就相当于类的功能了,反正我是不推荐使用),然后对结构体中的成员变量进行操作;
2. 专门有一些操作这个结构体的函数,比如初始化文件头部信息函数、获取文件头部信息(一个变量对应一个函数)等。
在分割的时候还会出现一些细节的问题:
在读取的时候不知道什么时候分割完成,因为我们还要在最后一个分割文件的头信息加上是最后一个分割文件的标识。这个问题会出现在:比如每次读取10个字节,待分割文件恰好有20字节,正好读取两次,每一次都是读取10个字节,所以我们不能够通过最后一次读取的字节个数来判断是否读取到最后一个字节,这时还要借助于文件信息中的文件长度来做比较,通过读取总字节的个数和原来字节的总个数相比较,当读取到的总字节大于等于原字节数的时候,就认为读取完成,可以做最后一个文件的标记了。
编程规范
在能够不使用指针的情况下不去使用指针,除非逼不得已的时候才去使用指针。绝大多数错误都是因为使用指针才出现的。
函数命名的时候不要和系统API重名。
代码相同的时候尽量不要去复制粘贴,这样很容易出错,还有就是如果真的出现重复代码的时候,那么我们就需要重构了。
文件分割的简单封装
// FileSplitAndMerge.h文件
#ifndef _MYFILESPLITANDMERGE_H_
#define _MYFILESPLITANDMERGE_H_
#include <vector>
#include <Windows.h>
#include <afx.h>
/**
* \brief 每个分割文件的头信息,其中包括了:
* wFlag 文件标志
* strFirstFileName 所有分割文件中的第一个文件名
* strNextFileName 下一个分割文件的文件名
* strOriginalFileName 原始文件的文件名
* uValidDataLength 有效的数据长度
*/
struct PARTFILEHEADERINFO
{
WORD wFlag;
WCHAR strFirstFileName[100];
WCHAR strNextFileName[100];
WCHAR strOriginalFileName[100];
UINT uValidDataLength;
};
class CMyPartFile : public CFile
{
public:
CMyPartFile();
~CMyPartFile();
void SetPartFileHeaderInfo(WORD wFlag, const CString &strFirstFileName, const CString &strNextFileName, const CString &strOriginalFileName, const UINT uReadBytesNumber);
/**
* \brief 获取存有有效数据的内存地址
* \return 返回存有有效数据的内存地址
*/
BYTE *GetValidData();
BOOL SaveValidDataToFile(const CString &strFolderName, const CString &strFileName, const BYTE* pData);
BOOL LoadFile(const CString &strFilePath);
WCHAR *GetFirstFileName();
WCHAR *GetNextFileName();
WCHAR *GetOriginalFileName();
UINT GetValidFileLength() const;
/**
* \brief 判断是否是我们自己的分割的文件
* \return 如果是我们分割的文件标志则返回TRUE,否则返回FALSE
*/
BOOL IsValidFile() const;
private:
PARTFILEHEADERINFO m_partFileHeaderInfo;
/**
* \brief 生成新的分割文件存放的目录
*/
CString m_strNewFolderName;
CString m_strFilePath;
BYTE *m_pValidData;
};
class CMyFileSplitAndMerge
{
public:
CMyFileSplitAndMerge();
~CMyFileSplitAndMerge();
public:
/**
* \brief 将原来的大文件按照指定的大小分割成一个个小文件
* \param strFolderName 文件夹名字
* \param strOriginalFileName 文件名字
* \param uSplitFileSize 分割文件大小
* \return 文件分割成功返回TRUE,否则返回FALSE
*/
BOOL Split(const CString &strFolderName, const CString &strOriginalFileName, const UINT uSplitFileSize);
/**
* \brief 根据选定的任意一个分割后的文件名,将这些所有的分割文件合并成成原来的文件
* \param strFolderName 文件夹名字
* \param strPartFileName 分割的任意一个文件的名字
* \return 文件合并成功返回TRUE,否则返回FALSE
*/
BOOL Merge(const CString &strFolderName, const CString &strPartFileName);
private:
/**
* \brief 获取所有的分割的文件
* \param strFolderName 文件夹名字
* \param strPartFileName 分割的任意一个文件的名字
* \return 如果全部找到分割的文件返回TRUE,否则返回FALSE
*/
BOOL GetAllPartFile(const CString &strFolderName, const CString &strPartFileName);
private:
std::vector<CMyPartFile *> m_vecAllPartFile;
CString m_strOriginalFileName;
};
#endif!//_MYFILESPLITANDMERGE_H_
// FileSplitAndMerge.cpp文件
#include <stdafx.h>
#include "MyFileSplitAndMerge.h"
CMyPartFile::CMyPartFile(): m_pValidData(nullptr)
{
}
CMyPartFile::~CMyPartFile()
{
if (m_pValidData)
{
delete[] m_pValidData;
}
}
void CMyPartFile::SetPartFileHeaderInfo(WORD wFlag, const CString& strFirstFileName, const CString& strNextFileName, const CString& strOriginalFileName, const UINT uReadBytesNumber)
{
m_partFileHeaderInfo.wFlag = MAKEWORD('P', 'o');
wcscpy_s(m_partFileHeaderInfo.strFirstFileName, strFirstFileName);
wcscpy_s(m_partFileHeaderInfo.strNextFileName, strNextFileName);
wcscpy_s(m_partFileHeaderInfo.strOriginalFileName, strOriginalFileName);
m_partFileHeaderInfo.uValidDataLength = uReadBytesNumber;
wchar_t *pTemp = nullptr;
WCHAR str[MAXBYTE] = { 0 };
wcscpy_s(str, m_partFileHeaderInfo.strOriginalFileName);
m_strNewFolderName = wcstok_s(str, L".", &pTemp);
}
BYTE* CMyPartFile::GetValidData()
{
CFileException fileException;
if (Open(m_strFilePath, CFile::modeRead|CFile::typeBinary, &fileException))
{
try
{
m_pValidData = new BYTE[m_partFileHeaderInfo.uValidDataLength];
}
catch (const std::bad_alloc &e)
{
MessageBoxA(nullptr, e.what(), "温馨提示", MB_OK);
}
memset(m_pValidData, 0, m_partFileHeaderInfo.uValidDataLength);
Seek(sizeof(PARTFILEHEADERINFO), CFile::begin);
Read(m_pValidData, m_partFileHeaderInfo.uValidDataLength);
Close();
}
else
{
m_pValidData = nullptr;
}
return m_pValidData;
}
BOOL CMyPartFile::SaveValidDataToFile(const CString &strFolderName, const CString &strFileName, const BYTE* pData)
{
BOOL bRet = FALSE;
CreateDirectory(strFolderName + TEXT("\\") + m_strNewFolderName, nullptr);
CString strFilePath = strFolderName + TEXT("\\") + m_strNewFolderName + TEXT("\\") + strFileName;
CFileException cFileException;
if (Open(strFilePath, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary, &cFileException))
{
Write(&m_partFileHeaderInfo, sizeof(PARTFILEHEADERINFO));
Write(pData, m_partFileHeaderInfo.uValidDataLength);
Close();
bRet = TRUE;
}
else
{
TCHAR strErrorMessage[MAXBYTE] = { 0 };
cFileException.GetErrorMessage(strErrorMessage, MAXBYTE);
MessageBox(nullptr, strErrorMessage, L"温馨提示", MB_OK);
}
return bRet;
}
BOOL CMyPartFile::LoadFile(const CString& strFilePath)
{
BOOL bRet = FALSE;
m_strFilePath = strFilePath;
TCHAR strErrorMessage[MAXBYTE] = { 0 };
CFileException fileException;
if (Open(m_strFilePath, CFile::modeRead|CFile::typeBinary, &fileException))
{
UINT uReadByteNumber = Read(&m_partFileHeaderInfo, sizeof(PARTFILEHEADERINFO));
if (uReadByteNumber != sizeof(PARTFILEHEADERINFO))
{
fileException.GetErrorMessage(strErrorMessage, MAXBYTE);
MessageBox(nullptr, strErrorMessage, TEXT("温馨提示"), MB_OK);
}
else
{
bRet = TRUE;
}
Close();
}
else
{
fileException.GetErrorMessage(strErrorMessage, MAXBYTE);
MessageBox(nullptr, strErrorMessage, TEXT("温馨提示"), MB_OK);
}
return bRet;
}
WCHAR* CMyPartFile::GetFirstFileName()
{
return m_partFileHeaderInfo.strFirstFileName;
}
WCHAR* CMyPartFile::GetNextFileName()
{
return m_partFileHeaderInfo.strNextFileName;
}
WCHAR* CMyPartFile::GetOriginalFileName()
{
return m_partFileHeaderInfo.strOriginalFileName;
}
UINT CMyPartFile::GetValidFileLength() const
{
return m_partFileHeaderInfo.uValidDataLength;
}
BOOL CMyPartFile::IsValidFile() const
{
return m_partFileHeaderInfo.wFlag == MAKEWORD('P', 'o');
}
CMyFileSplitAndMerge::CMyFileSplitAndMerge()
{
}
CMyFileSplitAndMerge::~CMyFileSplitAndMerge()
{
}
BOOL CMyFileSplitAndMerge::Split(const CString& strFolderName, const CString& strOriginalFileName, const UINT uFileSize)
{
BOOL bRet = FALSE;
CString strFileOriginalFilePath = strFolderName + TEXT("\\") + strOriginalFileName;
CFile file;
CFileException fileException;
if (file.Open(strFileOriginalFilePath, CFile::modeRead|CFile::typeBinary))
{
ULONGLONG ullFileLength = file.GetLength(), ullReadFileLength = 0;
UINT uReadBytesNumber, uPartFileCount = 0;
CString strFirstFileName = TEXT("0.part"), strCurFileName, strNextFileName;
BYTE *pData = new BYTE[uFileSize];
memset(pData, 0, uFileSize);
while (true)
{
PARTFILEHEADERINFO partFileHeaderInfo = { 0 };
uReadBytesNumber = file.Read(pData, uFileSize);
ullReadFileLength += uReadBytesNumber;
strCurFileName.Format(TEXT("%d.part"), uPartFileCount);
if (ullReadFileLength >= ullFileLength)
{
strNextFileName = L"NULL";
bRet = TRUE;
}
else
strNextFileName.Format(TEXT("%d.part"), ++uPartFileCount);
CMyPartFile myPartFile;
myPartFile.SetPartFileHeaderInfo(MAKEWORD('P', 'o'), strFirstFileName, strNextFileName, strOriginalFileName, uReadBytesNumber);
myPartFile.SaveValidDataToFile(strFolderName, strCurFileName, pData);
if (bRet) break;
memset(pData, 0, uFileSize);
}
delete[] pData;
file.Close();
}
else
MessageBox(nullptr, TEXT("打开文件失败"), TEXT("温馨提示"), MB_OK);
return bRet;
}
BOOL CMyFileSplitAndMerge::Merge(const CString& strFolderName, const CString& strPartFileName)
{
BOOL bRet = FALSE;
if (GetAllPartFile(strFolderName, strPartFileName))
{
CString strOriginalFilePath = strFolderName + L"\\" + m_strOriginalFileName;
CFile file(strOriginalFilePath, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary);
for (auto value : m_vecAllPartFile)
{
file.Write(value->GetValidData(), value->GetValidFileLength());
bRet = TRUE;
}
file.Close();
}
return bRet;
}
BOOL CMyFileSplitAndMerge::GetAllPartFile(const CString& strFolderName, const CString& strPartFileName)
{
BOOL bRet = FALSE;
CString strFirstFileName = TEXT(""), strCurFileName, strNextFileName;
CMyPartFile myPartFile;
if (myPartFile.LoadFile(strFolderName + L"\\" + strPartFileName))
{
strFirstFileName = myPartFile.GetFirstFileName();
// 下面是从第一个文件开始,一个一个的往后遍历,如果没有找到文件结尾的标志“NULL”,那么查找失败
strCurFileName = strFirstFileName;
while (true)
{
CMyPartFile *pMyPartFile = new CMyPartFile;
if (pMyPartFile->LoadFile(strFolderName + L"\\" + strCurFileName))
{
if (pMyPartFile->IsValidFile())
{
m_vecAllPartFile.push_back(pMyPartFile);
}
else
{
break;
}
strNextFileName = pMyPartFile->GetNextFileName();
if (strNextFileName != TEXT("NULL"))
{
strCurFileName = strNextFileName;
}
else
{
m_strOriginalFileName = pMyPartFile->GetOriginalFileName();
bRet = TRUE;
break;
}
}
else
{
break;
}
}
}
return bRet;
}
虽然上面实现了类的封装的功能,但是还是不规范,因为CMyPartFile这个类的功能比较模糊,它在使用的时候感觉不太好用,所以还需要对上面的类更进一步的封装和重构。这要到下一节再改进。