將多個檔案封裝為單一二進位容器(合併),並能還原為原始檔案群(分割),是安裝程式、資料打包、傳輸封裝的基礎需求。本文設計簡單高效的自訂格式:[檔名長度][檔名][檔案長度][檔案內容] 循環,提供完整的 C++Builder 實作 MergeFile 與 SeparateFile,含進度回報、錯誤處理、大檔案分塊讀寫。
合併檔案結構 (連續重複): +----------------+----------------+----------------+------------------+ | 檔名長度 (4B) | 檔名 (可變) | 檔案長度 (4B) | 檔案內容 (可變) | | int32 little-endian | int32 little-endian | +----------------+----------------+----------------+------------------+ | 檔名長度 (4B) | 檔名 ... | +----------------+----------------+----------------+------------------+ ... 結尾:檔案尾 (EOF)
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;
}
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),支援隨機存取
data.bin這套「長度前綴 + 內容」的簡單容器格式,雖不如 ZIP/TAR 功能豐富,但實作極簡、零相依、完全可控,是理解二進位封裝原理與快速落地專屬格式的最佳起點。