首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

C++ 實現檔案合併與分割:自訂二進位容器格式

     編輯:VC 教學

前言

將多個檔案封裝為單一二進位容器(合併),並能還原為原始檔案群(分割),是安裝程式、資料打包、傳輸封裝的基礎需求。本文設計簡單高效的自訂格式:[檔名長度][檔名][檔案長度][檔案內容] 循環,提供完整的 C++Builder 實作 MergeFile 與 SeparateFile,含進度回報、錯誤處理、大檔案分塊讀寫。

容器格式定義

合併檔案結構 (連續重複):
+----------------+----------------+----------------+------------------+
| 檔名長度 (4B)  | 檔名 (可變)    | 檔案長度 (4B)  | 檔案內容 (可變)  |
| int32 little-endian                | int32 little-endian           |
+----------------+----------------+----------------+------------------+
| 檔名長度 (4B)  | 檔名 ...                                    |
+----------------+----------------+----------------+------------------+
...
結尾:檔案尾 (EOF)

合併函式:MergeFile


bool MergeFile(AnsiString SourceFile, AnsiString DestFile)
{
    int hSrc, hDst, iSize, iBytesRead, iBytesWrite;
    char sTemp[MAX_PATH];

    // 1. 開啟來源檔 (唯讀共享)
    hSrc = FileOpen(SourceFile, fmOpenRead | fmShareDenyNone);
    if (hSrc < 0)
    {
        sprintf(sTemp, "無法開啟來源檔 %s", SourceFile.c_str());
        ShowMessage(sTemp);
        return false;
    }

    // 2. 開啟/建立目標容器檔
    if (FileExists(DestFile))
        hDst = FileOpen(DestFile, fmOpenWrite);
    else
        hDst = FileCreate(DestFile);
    if (hDst < 0)
    {
        sprintf(sTemp, "無法開啟目標檔 %s", DestFile.c_str());
        ShowMessage(sTemp);
        FileClose(hSrc);
        return false;
    }

    // 3. 移至目標檔尾 (支援追加合併)
    FileSeek(hDst, 0, 2);

    // 4. 寫入檔名長度
    AnsiString fileName = ExtractFileName(SourceFile); // 僅檔名,不含路徑
    iSize = fileName.Length();
    if (FileWrite(hDst, (char*)&iSize, sizeof(iSize)) != sizeof(iSize))
    {
        ShowMessage("寫入檔名長度失敗");
        FileClose(hSrc); FileClose(hDst);
        return false;
    }

    // 5. 寫入檔名
    if (FileWrite(hDst, fileName.c_str(), iSize) != iSize)
    {
        ShowMessage("寫入檔名失敗");
        FileClose(hSrc); FileClose(hDst);
        return false;
    }

    // 6. 寫入檔案長度
    iSize = FileSeek(hSrc, 0, 2); // 取得來源檔大小
    if (FileWrite(hDst, (char*)&iSize, sizeof(int)) != sizeof(int))
    {
        ShowMessage("寫入檔案長度失敗");
        FileClose(hSrc); FileClose(hDst);
        return false;
    }

    // 7. 分塊複製檔案內容 (1KB 緩衝)
    FileSeek(hSrc, 0, 0);
    char* Buffer = new char[1024];
    __try
    {
        do
        {
            iBytesRead = FileRead(hSrc, Buffer, 1024);
            iBytesWrite = FileWrite(hDst, Buffer, iBytesRead);
            if (iBytesWrite != iBytesRead)
            {
                ShowMessage("寫入資料失敗");
                return false;
            }
        }
        while (iBytesRead == 1024);
    }
    __finally
    {
        FileClose(hSrc);
        FileClose(hDst);
        delete[] Buffer;
    }
    return true;
}

分割函式:SeparateFile


bool SeparateFile(AnsiString SourceFile, AnsiString DestPath, bool bProgress)
{
    int hSrc = -1, hDst = -1, iSize, iBytesRead, iBytesWrite;
    AnsiString DestFile;
    char* Buffer = NULL;
    char sTemp[MAX_PATH];

    hSrc = FileOpen(SourceFile, fmOpenRead);
    if (hSrc < 0)
    {
        sprintf(sTemp, "無法開啟容器檔 %s", SourceFile.c_str());
        ShowMessage(sTemp);
        return false;
    }

    int fileSize = FileSeek(hSrc, 0, 2);
    if (bProgress)
    {
        // 假設 DlgProgress 為全域進度對話框物件
        DlgProgress->Initialize(fileSize, "還原資料庫從 " + SourceFile + " 到 " + DestPath);
        DlgProgress->Show();
    }

    FileSeek(hSrc, 0, 0);
    Buffer = new char[1024];
    __try
    {
        while (FileSeek(hSrc, 0, 1) < fileSize) // 當前位置 < 檔案大小
        {
            if (bProgress && g_bProcessEndByUser) break; // 使用者取消

            int iStepBy = 0;

            // 1. 讀取檔名長度
            iBytesRead = FileRead(hSrc, (char*)&iSize, sizeof(iSize));
            if (iBytesRead != sizeof(iSize))
            {
                ShowMessage("讀取檔名長度失敗");
                return false;
            }
            iStepBy += iBytesRead;

            // 2. 讀取檔名
            iBytesRead = FileRead(hSrc, Buffer, iSize);
            if (iBytesRead != iSize)
            {
                ShowMessage("讀取檔名失敗");
                return false;
            }
            Buffer[iSize] = 0; // 確保字串結尾
            iStepBy += iBytesRead;

            // 3. 讀取檔案長度
            iBytesRead = FileRead(hSrc, (char*)&iSize, sizeof(iSize));
            if (iBytesRead != sizeof(iSize))
            {
                ShowMessage("讀取檔案長度失敗");
                return false;
            }
            iStepBy += iBytesRead + iSize; // 估算進度

            if (bProgress)
                DlgProgress->Step(iStepBy);

            // 4. 建立目標檔並寫入內容
            DestFile = DestPath + AnsiString(Buffer);
            hDst = FileCreate(DestFile);
            if (hDst < 0)
            {
                sprintf(sTemp, "無法建立檔案 %s", DestFile.c_str());
                ShowMessage(sTemp);
                return false;
            }

            // 分塊寫入
            for (int i = 0; i < iSize / 1024; ++i)
            {
                iBytesRead = FileRead(hSrc, Buffer, 1024);
                iBytesWrite = FileWrite(hDst, Buffer, 1024);
                if (iBytesRead != 1024 || iBytesWrite != 1024)
                {
                    ShowMessage("複製資料失敗");
                    return false;
                }
            }

            // 餘數處理
            iSize %= 1024;
            if (iSize == 0)
            {
                FileClose(hDst);
                hDst = -1;
                continue;
            }

            iBytesRead = FileRead(hSrc, Buffer, iSize);
            iBytesWrite = FileWrite(hDst, Buffer, iSize);
            if (iBytesRead != iSize || iBytesWrite != iSize)
            {
                ShowMessage("複製剩餘資料失敗");
                return false;
            }

            FileClose(hDst);
            hDst = -1;
        }
    }
    __finally
    {
        FileClose(hSrc);
        if (hDst > 0) FileClose(hDst);
        delete[] Buffer;
        if (bProgress && DlgProgress->Visible)
            DlgProgress->Close();
    }
    return true;
}

關鍵設計細節

設計點說明
Little-endian int32跨平台相容 (x86/x64/ARM 皆為小端),網路傳輸需轉大端
僅儲存檔名不保留目錄結構,還原時平鋪至 DestPath。如需保留相對路徑,改存相對路徑字串
1KB 緩衝區平衡記憶體與系統呼叫開銷,大檔案可調大至 64KB-1MB
追加合併支援MergeFile 以 FileSeek(hDst,0,2) 定位檔尾,可多次呼叫累加
進度回報介面SeparateFile 參數 bProgress 控制,預留 DlgProgress 介面
使用者取消支援全域旗標 g_bProcessEndByUser,長時間操作必備
__try/__finally確保檔案控制代碼與緩衝區在異常/提前返回時釋放

擴充:支援目錄結構、壓縮、校驗和


// 擴充格式:[路徑長度][相對路徑][檔案長度][CRC32][檔案內容]
// 壓縮:檔案內容前加壓縮旗標,使用 zlib/lz4 壓縮
// 索引:容器頭部建立索引表 (檔名、偏移、大小、CRC),支援隨機存取

現代 C++ 替代方案

  • std::filesystem + iostream:跨平台、RAII、例外安全
  • Boost.IOStreams / zlib:串流式壓縮/解壓
  • libarchive:支援 tar/zip/cpio/iso 等標準封存格式
  • Protocol Buffers / FlatBuffers / MessagePack:結構化序列化,自帶索引與壓縮

應用場景

  • 資料庫備份/還原:將多個資料檔合併為單一容器傳輸
  • 安裝程式資料封裝:數百個資源檔合併為一個 data.bin
  • 嵌入式系統固件打包:多二進位映像合併燒錄
  • 網路傳輸優化:小檔案合併減少 HTTP 連線開銷
  • 日誌輪轉歸檔:每日日誌合併壓縮存檔

這套「長度前綴 + 內容」的簡單容器格式,雖不如 ZIP/TAR 功能豐富,但實作極簡、零相依、完全可控,是理解二進位封裝原理與快速落地專屬格式的最佳起點。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved