Windows Practice_文件_文件分割器(二)

文件分割器實現時注意的幾點

分割文件的頭信息結構體的設計,如果裏面需要存儲字符串的信息,我們必須使用字符串數組來存儲,而不能使用類似於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這個類的功能比較模糊,它在使用的時候感覺不太好用,所以還需要對上面的類更進一步的封裝和重構。這要到下一節再改進。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章