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

C 語言文件操作實戰:在文件頭動態寫入統計信息的技巧

     編輯:關於C語言

業務場景

日誌/計費系統需將記錄追加寫入文本文件,且第一行始終保持統計摘要:總記錄數|總費用。每次寫入新記錄後,需原子更新首行統計值。

文件格式示例:

3|300
user1|productA|100
user2|productB|100
user3|productC|100

核心難點

  • 文件頭覆寫:首行長度可能變化 (如 9→10 字符),直接 fseek(0) 覆寫會殘留舊字符或截斷後續數據。
  • 並發安全:多進程/線程追加寫入時的競態條件。
  • 崩潰一致性:寫入數據行後更新統計行前掉電,導致統計與數據不一致。

方案一:覆寫模式 (適用於單進程、低頻、小文件)


#include 
#include 
#include 

#define RECORD_FMT "%s|%s|%d\n"   // 用戶|產品|費用
#define STAT_FMT   "%d|%d\n"      // 記錄數|總費用

int appendRecordWithStat(const char* filename, const char* user, const char* product, int fee)
{
    FILE* fp = fopen(filename, "r+"); // 讀寫模式,文件必須存在
    if (!fp) {
        fp = fopen(filename, "w+");   // 不存在則創建
        if (!fp) return -1;
    }

    /* 1. 讀取並解析首行統計 */
    char statLine[64];
    int count = 0, totalFee = 0;
    if (fgets(statLine, sizeof(statLine), fp)) {
        sscanf(statLine, STAT_FMT, &count, &totalFee);
    }

    /* 2. 定位文件末尾追加數據行 */
    fseek(fp, 0, SEEK_END);
    long dataPos = ftell(fp); // 記錄數據起始位置 (用於回滾)
    fprintf(fp, RECORD_FMT, user, product, fee);
    fflush(fp); // 強制落盤

    /* 3. 更新統計 */
    count++;
    totalFee += fee;

    /* 4. 回到文件頭覆寫統計行 */
    fseek(fp, 0, SEEK_SET);
    fprintf(fp, STAT_FMT, count, totalFee);
    fflush(fp);

    /* 5. 處理統計行變長導致的覆蓋問題:
     * 若新統計行比舊行長,會覆蓋數據行開頭;
     * 若較短,會殘留舊字符。
     * 簡單修復:統計行固定寬度 (如 %010d|%010d\n) 或重寫整個文件。
     */
    fclose(fp);
    return 0;
}

方案二:固定寬度統計行 (零拷貝、高性能)

預留足夠寬度,如 %010d|%010d\n (固定 23 字節),確保覆寫不越界、無殘留。


#define STAT_FMT_FIXED "%010d|%010d\n" // 固定 23 字節

// 寫入時:
fseek(fp, 0, SEEK_SET);
fprintf(fp, STAT_FMT_FIXED, count, totalFee);
// 讀取時:
fgets(buf, 24, fp); // 讀固定長度
sscanf(buf, "%d|%d", &count, &totalFee); // sscanf 自動跳過前導零
優點:O(1) 更新、無需移動數據、適合高頻寫入。
缺點:統計值有上限 (10 位數),人類可讀性稍差。

方案三:寫時復制 / 臨時文件原子替換 (強一致性、並發安全)


int appendRecordAtomic(const char* filename, const char* user, const char* product, int fee)
{
    char tmpName[FILENAME_MAX];
    snprintf(tmpName, sizeof(tmpName), "%s.tmp", filename);

    FILE* fpSrc = fopen(filename, "r");
    FILE* fpDst = fopen(tmpName, "w");
    if (!fpDst) { if (fpSrc) fclose(fpSrc); return -1; }

    int count = 0, totalFee = 0;
    char line[256];

    // 1. 讀取舊統計行
    if (fpSrc && fgets(line, sizeof(line), fpSrc))
        sscanf(line, "%d|%d", &count, &totalFee);

    // 2. 寫入新統計行
    count++; totalFee += fee;
    fprintf(fpDst, "%d|%d\n", count, totalFee);

    // 3. 複製舊數據行
    if (fpSrc) {
        while (fgets(line, sizeof(line), fpSrc))
            fputs(line, fpDst);
        fclose(fpSrc);
    }

    // 4. 追加新記錄
    fprintf(fpDst, "%s|%s|%d\n", user, product, fee);
    fclose(fpDst);

    // 5. 原子替換 (POSIX rename / Windows MoveFileEx)
    #ifdef _WIN32
        MoveFileEx(tmpName, filename, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
    #else
        rename(tmpName, filename); // 原子操作
    #endif
    return 0;
}
優點:崩潰安全 (原文件不變)、並發友好 (配合文件鎖)、無統計行長度限制。
缺點:每次寫入需全量複製文件,大文件性能差。

方案四:內存映射 + 原子 CAS (高性能、大文件、進程間共享)


// 概念框架 (Linux mmap / Windows MapViewOfFile)
// 1. 文件結構: [Header: count, totalFee, dataOffset] [Data...]
// 2. 映射整個文件到內存
// 3. 使用原子操作 (__sync_fetch_and_add / InterlockedIncrement) 更新 count/totalFee
// 4. 在 dataOffset 追加數據 (需文件擴展 ftruncate/SetEndOfFile)
// 5. msync/FlushViewOfFile 落盤
// 適用於極高併發、GB 級日誌、數據庫 WAL 風格。

最佳實踐總結表

場景推薦方案關鍵點
單進程、低頻 (<10/s)、小文件 (<1MB)方案一 + 固定寬度代碼最簡單,fflush 確保落盤
單進程、高頻、中等文件方案二 (固定寬度覆寫)O(1) 更新,無拷貝開銷
多進程/崩潰安全要求高方案三 (臨時文件+原子重命名)POSIX rename / MoveFileEx 原子性保證
極高性能、大文件、共享內存方案四 (mmap + 原子指令)零拷貝、頁面級落盤、需處理擴展

防禦性編程清單

  1. 檢查所有系統調用返回值:fopen/fseek/fprintf/fflush/rename。
  2. 使用 fflush + fsync/_commit:確保數據真正寫入磁盤物理介質,而非頁面緩存。
  3. 文件鎖:多進程場景必加 flock/LockFileEx。
  4. 統計行格式版本化:首字節寫版本號,便於未來格式升級兼容。
  5. 單元測試覆蓋:掉電模擬 (kill -9)、併發壓測、統計行變長、磁盤滿。

總結

「文件頭統計行動態更新」看似簡單,實則涉及文件系統語義、崩潰一致性、併發控制、性能權衡。根據業務規模選擇:

  • **簡單場景** → 固定寬度覆寫 (方案二)
  • **可靠性優先** → 臨時文件原子替換 (方案三)
  • **極致性能** → mmap + 原子操作 (方案四)

掌握這三種模式,應對 99% 的文件統計更新需求綽綽有餘。

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