用途:像一些安装程序经常会用到的是把一个很大的文件切割成较小的文件,安装的时候从每个小的文件块中读取内容。还有一些网络下载工具像快车等一般是通过多线程对同一个文件进行下载,就是对同一文件进行分块下载,然后将本地的文件块合并就可以得到最终的文件,提高了效率。
其实文件分割和合并的方法就是自定义的一个数据结构,通过数据结构获得文件信息以进行操作。在这里我们整体过程是这样的:
--------------- ------------------
中间文件1 --- >>> 文件信息头
--------------- 源文件内容
中间文件2 ------------------
...
...
--------------
中间文件n
文件信息头是自定义的结构,在这里我们定义成这样:
typedef struct tagTHREADSECT
{
TCHAR cFilename[40]; // 被分割的文件名.
DWORD nFileSize; // 文件长度.
DWORD nNumber; // 总分割数.
DWORD nNo; // 子文件序号.
DWORD nStart; // 分割的起始位置.
DWORD nEnd; // 分割的起始位置.
} THREADSECT;
首先包含被分割或合并的源文件名,再有文件长度,文件总分割数和子文件序号,这些可以用于验证分割或合并的完成性。最后两个为此块文件包含源文件的内容,在源文件中的起始位置信息。
好了现在说一说实现,就分割而言可以按固定大小或固定数目两个种分割方法。两种方法只有分的块数不同其他实现基本一样。接着就是分割或合并,在这里我们可以使用多线程实现对每一块文件的操作提高效率,所以需要两个线程一个负责分割另一个负责合并。使用到多线程对同一文件的操作则还需要考虑对文件的保护,可以使用关键区域加锁方法实现。
再需要定义一个结构体或类:CSectInfo
包含:文件目录 (CString) 信息头 (THREADSECT) 和 文件指针(FILE*)
此模块可以用来向进程传送数据,进程通过此模块就可以进行独立的操作。
文件目录用来保证文件位置,信息头包含了分块信息,文件指针则保存了源文件指针,以便在进程里对源文件进行操作。
下面是代码,对文件的分割和合并都封装到一个CThreadFCut类里:
- // ThreadFCut.h: 定义CThreadFCut类.
- #ifndef _THREADFCUT_H__INCLUDED
- #define _THREADFCUT_H__INCLUDED
- #include <sys/stat.h> // 加入状态显示头文件.
- #define MCHAR(str) (str.GetBuffer(str.GetLength()))
- typedef struct tagTHREADSECT
- {
- TCHAR cFilename[40]; // 被分割的文件名.
- DWORD nFileSize; // 文件长度.
- DWORD nNumber; // 总分割数.
- DWORD nNo; // 子文件序号.
- DWORD nStart; // 分割的起始位置.
- DWORD nEnd; // 分割的起始位置.
- } THREADSECT;
- class CSectInfo
- {
- public:
- CString strFilePath; // 分割后的文件保存目录.
- THREADSECT sect; // 分割的起始和终止位置.
- FILE * fp; // 文件指针.
- };
- class CThreadFCut
- {
- public:
- CThreadFCut();
- virtual ~CThreadFCut();
- public:
- //等数目的分割
- BOOL DoVolumeCut(CString strCurrDir,CString strSaveDir, CString strFileName,int nSectNum);
- //等体积的分割
- BOOL DoUnitCut(CString strCurrDir,CString strSaveDir, CString strFileName,long nSize);
- //线程合并
- BOOL DoThreadCombine(CString strCurrDir,CString strSaveDir, CString strFileName, int nSectNum);
- //非线程合并
- BOOL NonThreadCombine(CString strCurrDir,CString strSaveDir,CString strFileName,int nSectNum);
- //判断文件是否存在
- BOOL FileExists(LPCTSTR lpszFileName);
- //检查是否为分割后的块文件
- BOOL CheckFile(CString &strFileName,int &nSectNum);
- //实现自动合并
- void DoSelfBind();
- //分割文件路径
- void BreakFileName(CString strFileName,CString &strPath, CString &strShortName);
- private:
- //线程和主线程共享的数据块
- CSectInfo *sectinfo;
- protected:
- //合并线程
- static UINT ThreadFileCombine(void* pParam);
- //分割线程
- static UINT ThreadFileCut(void* pParam);
- //实现加锁保护
- static CRITICAL_SECTION m_csDataLock;
- //分割文件块计数
- static int m_nCount;
- };
- #endif
下面是实现:
- // FThreadFCut.cpp: 实现CThreadFCut类.
- //
- #include "stdafx.h"
- #include "ThreadFCut.h"
- CRITICAL_SECTION CThreadFCut::m_csDataLock;
- int CThreadFCut::m_nCount=0;
- //---------------------------------------------------------------------------
- CThreadFCut::CThreadFCut()
- {
- InitializeCriticalSection(&CThreadFCut::m_csDataLock);
- }
- //---------------------------------------------------------------------------
- CThreadFCut::~CThreadFCut()
- {
- DeleteCriticalSection(&CThreadFCut::m_csDataLock);
- }
- //---------------------------------------------------------------------------
- BOOL CThreadFCut::FileExists(LPCTSTR lpszFileName)
- {
- DWORD dwAttributes = GetFileAttributes(lpszFileName);
- if (dwAttributes == 0xFFFFFFFF)
- return FALSE;
- if ((dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
- == FILE_ATTRIBUTE_DIRECTORY)
- {
- return FALSE;
- }
- else
- {
- return TRUE;
- }
- }
- //---------------------------------------------------------------------------
- // 用多线程对文件进行分割的线程控制函数.
- UINT CThreadFCut::ThreadFileCut(void* pParam)
- {
- CSectInfo *pInfo=(CSectInfo*)pParam;
- char *buffer;
- FILE *fpwrite;
- DWORD nSize;
- // 建立子文件名.
- CString strChildFileName;
- strChildFileName.Format("%s_%d",pInfo->strFilePath+
- pInfo->sect.cFilename,pInfo->sect.nNo);
- // 打开子文件.
- if((fpwrite=fopen(strChildFileName,"wb"))==NULL){
- return 1;
- }
- // 设置文件写指针起始位置.
- fseek(fpwrite,0,SEEK_SET);
- // 写分割信息.
- fwrite(&pInfo->sect,1,sizeof(THREADSECT),fpwrite);
- DWORD nPos=pInfo->sect.nStart;
- nSize =pInfo->sect.nEnd-nPos;
- buffer= new char[nSize];
- // 设置文件读指针起始位置.
- fseek(pInfo->fp,nPos,SEEK_SET);
- // 把读出文件数据.
- fread(buffer,1,nSize,pInfo->fp);
- // 把读出的文件数据写入到子文件.
- fwrite(buffer,1,nSize,fpwrite);
- delete[] buffer;
- // 关闭写文件.
- fclose(fpwrite);
- // 计数.
- CThreadFCut::m_nCount++;
- return 0;
- }
- //---------------------------------------------------------------------------
- // 用多线程对分割文件进行合并的线程控制函数.
- UINT CThreadFCut::ThreadFileCombine(void* pParam)
- {
- CSectInfo *pInfo=(CSectInfo*)pParam;
- char *buffer;
- FILE *fpread;
- DWORD nSize;
- THREADSECT fileHead;
- // 建立子文件名.
- CString strChildFileName;
- strChildFileName.Format("%s_%d",pInfo->strFilePath+
- pInfo->sect.cFilename, pInfo->sect.nNo);
- // 打开读文件(子文件).
- if((fpread=fopen(strChildFileName,"rb"))==NULL){
- return 1;
- }
- // 把文件读指针放在文件头.
- fseek(fpread,0,SEEK_SET);
- // 读分割信息.
- fread(&fileHead,1,sizeof(THREADSECT),fpread);
- // 检验文件序号是否正确.
- if(fileHead.nNo!=pInfo->sect.nNo){
- return 1;
- }
- // 设置文件读指针起始位置.
- fseek(fpread,sizeof(THREADSECT),SEEK_SET);
- DWORD nPos=fileHead.nStart;
- nSize =fileHead.nEnd-nPos;
- buffer= new char[nSize];
- // 把读出子文件数据.
- fread(buffer,1,nSize,fpread);
- // 保护合并文件.
- EnterCriticalSection(&CThreadFCut::m_csDataLock);
- // 设置文件写指针起始位置.
- fseek(pInfo->fp,nPos,SEEK_SET);
- // 把文件数据写入到合并文件.
- fwrite(buffer,1,nSize,pInfo->fp);
- LeaveCriticalSection(&CThreadFCut::m_csDataLock);
- delete[] buffer;
- // 关闭读文件.
- fclose(fpread);
- // 计数.
- CThreadFCut::m_nCount++;
- return 0;
- }
- //---------------------------------------------------------------------------
- // 实现等体积的文件分割.
- BOOL CThreadFCut::DoVolumeCut(CString strCurrDir,CString strSaveDir,
- CString strFileName, int nSectNum)
- {
- CString strCurrFile=strCurrDir+strFileName;
- if(!FileExists(strCurrFile)) return FALSE;
- ASSERT(nSectNum>1);
- ASSERT(!strCurrFile.IsEmpty());
- struct _stat ST;
- FILE *fpread;
- // 获取文件长度.
- _stat(strCurrFile, &ST);
- UINT nFilesize=ST.st_size;
- // 计算分割段的大小.
- DWORD nSize = nFilesize/nSectNum;
- // 把计数器清零.
- m_nCount=0;
- // 打开文件,被切割的文件名.
- if((fpread=fopen(strCurrFile,"rb"))==NULL){
- return FALSE;
- }
- // 给每个线程的信息结构申请内存.
- sectinfo=new CSectInfo[nSectNum];
- // 创建线程.
- for(int i=0;i<nSectNum;i++)
- {
- sectinfo[i].fp=fpread;
- sectinfo[i].strFilePath = strSaveDir;
- strcpy(sectinfo[i].sect.cFilename,MCHAR(strFileName));
- sectinfo[i].sect.nFileSize=nFilesize;
- sectinfo[i].sect.nNumber=nSectNum;
- sectinfo[i].sect.nNo=i;
- if(i<nSectNum-1){
- sectinfo[i].sect.nStart=i*nSize;
- sectinfo[i].sect.nEnd=(i+1)*nSize;
- }
- else{
- sectinfo[i].sect.nStart=i*nSize;
- sectinfo[i].sect.nEnd=nFilesize;
- }
- #ifdef _DEBUG
- TRACE("Starting and ending pos for the section: %d %d %d/n",
- i,sectinfo[i].sect.nStart,sectinfo[i].sect.nEnd);
- #endif //_DEBUG
- AfxBeginThread(ThreadFileCut,§info[i],THREAD_PRIORITY_HIGHEST);
- }
- // 等待所有线程结束.
- while(m_nCount!=nSectNum) Sleep(5);
- delete[] sectinfo;
- fclose(fpread);
- // 删除被分割文件.
- DeleteFile(strCurrFile);
- return TRUE;
- }
- //---------------------------------------------------------------------------
- // 实现等单位的文件分割.
- BOOL CThreadFCut::DoUnitCut(CString strCurrDir,CString strSaveDir, CString strFileName, long nSize)
- {
- CString strCurrFile=strCurrDir+strFileName;
- if(!FileExists(strCurrFile)) return FALSE;
- ASSERT(nSize>1);
- ASSERT(!strCurrFile.IsEmpty());
- struct _stat ST;
- FILE *fpread;
- // 获取文件长度.
- _stat(strCurrFile, &ST);
- UINT nFilesize=ST.st_size;
- // 计算分割段的段数.
- int nSectNum= nFilesize/nSize;
- // 有多余的长度.
- if(nFilesize%nSize!=0) nSectNum ++;
- // 把计数器清零.
- m_nCount=0;
- // 打开文件,被切割的文件名.
- if((fpread=fopen(strCurrFile,"rb"))==NULL){
- return FALSE;
- }
- // 给每个线程的信息结构申请内存.
- sectinfo=new CSectInfo[nSectNum];
- // 创建线程.
- for(int i=0;i<nSectNum;i++)
- {
- sectinfo[i].fp=fpread;
- sectinfo[i].strFilePath = strSaveDir;
- strcpy(sectinfo[i].sect.cFilename,MCHAR(strFileName));
- sectinfo[i].sect.nFileSize=nFilesize;
- sectinfo[i].sect.nNumber=nSectNum;
- sectinfo[i].sect.nNo=i;
- if(i<nSectNum-1){
- sectinfo[i].sect.nStart=i*nSize;
- sectinfo[i].sect.nEnd=(i+1)*nSize;
- }
- else{
- sectinfo[i].sect.nStart=i*nSize;
- sectinfo[i].sect.nEnd=nFilesize;
- }
- #ifdef _DEBUG
- TRACE("Starting and ending pos for the section: %d %d %d/n",
- i,sectinfo[i].sect.nStart,sectinfo[i].sect.nEnd);
- #endif //_DEBUG
- AfxBeginThread(ThreadFileCut,§info[i],THREAD_PRIORITY_HIGHEST);
- }
- // 等待所有线程结束.
- while(m_nCount!=nSectNum) Sleep(5);
- delete[] sectinfo;
- fclose(fpread);
- // 删除被分割文件.
- DeleteFile(strCurrFile);
- return TRUE;
- }
- //---------------------------------------------------------------------------
- // 用多线程对被分割文件还原.
- BOOL CThreadFCut::DoThreadCombine(CString strCurrDir,CString strSaveDir,
- CString strFileName, int nSectNum)
- {
- CString strSaveFile=strSaveDir+strFileName;
- CString strCurrFile=strCurrDir+strFileName;
- CString strChildFileName;
- strChildFileName.Format("%s_0",strCurrFile);
- if(!FileExists(strChildFileName))
- return FALSE;
- FILE *fpwrite;
- // 把计数器清零.
- m_nCount=0;
- // 打开文件,应为切割前的文件名.
- if((fpwrite=fopen(strSaveFile,"wb"))==NULL){
- return FALSE;
- }
- //给每个线程的信息结构申请内存.
- sectinfo=new CSectInfo[nSectNum];
- for(int i=0;i<nSectNum;i++)
- {
- sectinfo[i].fp=fpwrite;
- sectinfo[i].strFilePath = strCurrDir;
- strcpy(sectinfo[i].sect.cFilename,MCHAR(strFileName));
- sectinfo[i].sect.nNo=i;
- AfxBeginThread(ThreadFileCombine,§info[i],THREAD_PRIORITY_HIGHEST);
- }
- // 等待所有线程结束.
- while(m_nCount!=nSectNum) Sleep(5);
- for(int i=0;i<nSectNum;i++){
- strChildFileName.Format("%s_%d",strCurrFile,i);
- DeleteFile(strChildFileName);
- }
- delete[] sectinfo;
- fclose(fpwrite);
- return TRUE;
- }
- //---------------------------------------------------------------------------
- // 没有采用多线程时对分割后文件的合并.
- BOOL CThreadFCut::NonThreadCombine(CString strCurrDir,CString strSaveDir,
- CString strFileName,int nSectNum)
- {
- CString strSaveFile=strSaveDir+strFileName;
- CString strCurrFile=strCurrDir+strFileName;
- THREADSECT fileHead;
- FILE *fpread, *fpwrite;
- CString strChildFileName;
- // 打开文件,应为切割前的文件名.
- if((fpwrite=fopen(strSaveFile,"wb"))==NULL){
- return FALSE;
- }
- for(int i=0;i<nSectNum;i++)
- {
- strChildFileName.Format("%s_%d",strCurrFile,i);
- if(!FileExists(strChildFileName)) return FALSE;
- // 打开文件.
- if((fpread=fopen(strChildFileName,"rb"))==NULL)
- return FALSE;
- // 把文件读指针放在文件头.
- fseek(fpread,0,SEEK_SET);
- // 读分割信息.
- fread(&fileHead,1,sizeof(THREADSECT),fpread);
- // 获得写入数据时文件的起始位置.
- DWORD nPos=fileHead.nStart;
- // 设置文件写指针起始位置.
- fseek(fpwrite,nPos,SEEK_SET);
- int c;
- // 把文件数据写入到子文件.
- while((c=fgetc(fpread))!=EOF)
- {
- fputc(c,fpwrite);
- nPos++;
- if(nPos==fileHead.nEnd) break;
- }
- fclose(fpread);
- DeleteFile(strChildFileName);
- }
- fclose(fpwrite);
- return TRUE;
- }
- //---------------------------------------------------------------------------
- // 获得分割后文件的信息.
- BOOL CThreadFCut::CheckFile(CString & strFileName,int &nSectNum)
- {
- THREADSECT fileHead;
- FILE *fp;
- // 从一个子文件中获得分割文件的信息.
- // 打开文件.
- if((fp=fopen(strFileName,"rb"))==NULL)
- return FALSE;
- struct _stat ST;
- // 读文件头.
- fread(&fileHead,1,sizeof(THREADSECT),fp);
- fclose(fp);
- // 获取文件长度.
- _stat(strFileName, &ST);
- UINT nFilesize=ST.st_size;
- // 获得被分割的文件名.
- strFileName = fileHead.cFilename;
- // 获得分割段数.
- nSectNum=fileHead.nNumber;
- // 由保存的THREADSECT计算文件长度.
- UINT nFilesizeA =(fileHead.nEnd-fileHead.nStart)+sizeof(THREADSECT);
- // 文件校验.
- if(nFilesizeA==nFilesize) return TRUE;
- return FALSE;
- }
- //---------------------------------------------------------------------------
- // 实现被分割文件的自身合并.
- void CThreadFCut::DoSelfBind()
- {
- CFileFind finder;
- TCHAR szDir[MAX_PATH];
- // 获得文件的当前目录.
- GetCurrentDirectory(MAX_PATH, szDir);
- CString strFileName;
- int nSectNum;
- BOOL bWorking = finder.FindFile(CString(szDir)+"//*");
- BOOL bFind=FALSE;
- while (bWorking)
- {
- bWorking = finder.FindNextFile();
- // 判别是否为目录.
- if(!finder.IsDirectory()){
- CString strTempName=finder.GetFilePath();
- if(CheckFile(strTempName,nSectNum)){
- strFileName=strTempName;
- bFind=TRUE;
- break;
- }
- }
- }
- // 没有找到分割后的子文件.
- if(!bFind){
- AfxMessageBox("Fail to find any splitted file!");
- return;
- }
- CString strFilePath,strShortFileName;
- // 分离文件路径和短文件名.
- BreakFileName(strFileName,strFilePath,strShortFileName);
- // 也可以用多线程法对文件合并:
- // DoThreadCombine(strFilePath,strFilePath,strShortFileName,nSectNum);
- NonThreadCombine(strFilePath,strFilePath,strShortFileName,nSectNum);
- };
- //---------------------------------------------------------------------------
- // 把全程文件名分解成文件路径和短文件名.
- void CThreadFCut::BreakFileName(CString strFileName,
- CString &strPath, CString &strShortName)
- {
- // 分离文件路径和短文件名.
- int nDash=strFileName.ReverseFind('//');
- strPath=strFileName.Left(nDash+1);
- strShortName=strFileName.Right(strFileName.GetLength()-nDash-1);
- }
主要程序就这些了,可以通过这样调用
CThreadFCut FCut;
// 文件分割.
FCut.DoVolumeCut("","","grub.exe",4);
FCut.DoUnitCut("","","grub.exe",30000);
// 文件分割后合并.
FCut.DoThreadCombine("","","grub.exe",4);
FCut.NonThreadCombine("f://","f://","grub.exe",4);
//SelfBind
FCut.DoSelfBind();
把路径弄好就可以使用了,所有代码都经过调试没有发现什么错误.有想学习的可以复制使用。