程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C基礎 大文件讀取通過標准庫,c基礎

C基礎 大文件讀取通過標准庫,c基礎

編輯:關於C語言

C基礎 大文件讀取通過標准庫,c基礎


引言 - 問題的構建

   C大部分讀取文件的時候采用fgetc, 最近在使用過程中發現性能不是很理想.都懂得fgetc每次只能讀取一個字符, IO操作太頻繁. 

所以性能低. 本文希望通過標准庫函數fread 函數構建讀取緩沖區來優化這個瓶頸

在正式開始實驗總結之前, 傳一個VS C/C++ 開發的技巧給大家, 天外飛仙~ .

  M$忽略C++太久了,對於C直接放棄, 在其Visual Studio IDE中. 但是吧在Window 還是它的IDE寫C 系列語言最爽.

現在很流行一個低端套路是, Window VS 開發, Linux上部署. 而我們的問題就是關於這個, 這種不同平台的開發和部署,

存在一個坑就是編碼問題. 而這裡就是希望完美的解決這個編碼問題.

  解決基准是選用UTF-8 帶簽名的編碼, VS , GCC都能編譯通過. 那就一言不合上圖了, 首先定位VC模板文件

將上面模板文件備份一份, 復制一份, 用VS打開復制的那份

高級選項另存為上面編碼格式. 最終保存替換原先的模板文件. 從此以後, 編碼問題 perfect! 繼續扯一點, 隨著寫代碼時間增長, VS的依賴已經不重要的.

可惜喜歡打游戲, 還是被window 游戲機綁定了. 真的是離開I可以, 請付出代價~ /(ㄒoㄒ)/~~

 

前言 - 實驗驗證

  這裡驗證的是fgetc 和 fread讀取性能的對比. 在說之前,  先介紹個測試宏

// 簡單的time幫助宏
#ifndef TIME_PRINT
#define _STR_TIME_PRINT "The current code block running time:%lf seconds\n"
#define TIME_PRINT(code) \
    do {\
        clock_t __st, __et;\
        __st = clock();\
        code\
        __et = clock();\
        printf(_STR_TIME_PRINT, (0.0 + __et - __st) / CLOCKS_PER_SEC);\
    } while(0)
#endif // !TIME_PRINT

非常還用, 將代碼塊插入到 code中, 就可以使用了. 那繼續了, 第一個測試的主體內容是, 實驗一 fread對比fgetc

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define _STR_DATA    "data.txt"
#define _INT_DATA    (1024*1024*32)

// 測試 fgetc 性能
void test_fgetc(void);

// 測試 fread 性能
void test_fread(void);

//
// 測試C大文件處理方式
//
int main(int argc, char * argv[]) {

    // 先構建測試環境
    FILE * txt = fopen(_STR_DATA, "r");
    if (NULL == txt) {
        txt = fopen(_STR_DATA, "w");
        if (NULL == txt) {
            fprintf(stderr, "main fopen w " _STR_DATA " error!\n");
            exit(EXIT_FAILURE);
        }

        // 開始寫入數據
        for (int i = 0; i < _INT_DATA; ++i)
            fprintf(txt, "%d", i);
    }
    fclose(txt);

    // 開始測試數據, 分批測試
    TIME_PRINT({
        test_fgetc();
    });

    TIME_PRINT({
        test_fread();
    });

    return 0;
}

其中兩個測試函數如下.

//
// 測試 fgetc 性能
//
void 
test_fgetc(void) {
    FILE * txt = fopen(_STR_DATA, "r");
    if (NULL == txt) {
        fprintf(stderr, "test_fgetc fopen w " _STR_DATA " error!\n");
        return;
    }

    size_t cnt = 0;
    int c;

    while ((c = fgetc(txt)) != EOF)
        ++cnt;

    fclose(txt);

    printf("test_fgetc cnt = %d\n", cnt);
}

// 
// 測試 fread 性能
//
void test_fread(void) {
    FILE * txt = fopen(_STR_DATA, "r");
    if (NULL == txt) {
        fprintf(stderr, "test_fread fopen w " _STR_DATA " error!\n");
        return;
    }

    size_t cnt = 0;
    char buf[BUFSIZ];

    for (;;) {
        int rn = fread(buf, sizeof(char), BUFSIZ, txt);
        // 存在信號中斷情況, 不考慮
        cnt += rn;
        if (rn < BUFSIZ)
            break;
    }

    fclose(txt);

    printf("test_fread cnt = %d\n", cnt);
}

測試主要思路是. 

  a. 構建差不多是200-300Mb的數據文件

  b. 通過fgetc 完畢, 輸出時間

  c. 通過fread 完畢, 輸出時間

測試結果如下

我們發現, fread構建緩沖區有質的飛躍.

附錄測試完整內容 file_test.c

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define _STR_DATA    "data.txt"
#define _INT_DATA    (1024*1024*32)

// 簡單的time幫助宏
#ifndef TIME_PRINT
#define _STR_TIME_PRINT "The current code block running time:%lf seconds\n"
#define TIME_PRINT(code) \
    do {\
        clock_t __st, __et;\
        __st = clock();\
        code\
        __et = clock();\
        printf(_STR_TIME_PRINT, (0.0 + __et - __st) / CLOCKS_PER_SEC);\
    } while(0)
#endif // !TIME_PRINT

// 測試 fgetc 性能
void test_fgetc(void);

// 測試 fread 性能
void test_fread(void);

//
// 測試C大文件處理方式
//
int main(int argc, char * argv[]) {

    // 先構建測試環境
    FILE * txt = fopen(_STR_DATA, "r");
    if (NULL == txt) {
        txt = fopen(_STR_DATA, "w");
        if (NULL == txt) {
            fprintf(stderr, "main fopen w " _STR_DATA " error!\n");
            exit(EXIT_FAILURE);
        }

        // 開始寫入數據
        for (int i = 0; i < _INT_DATA; ++i)
            fprintf(txt, "%d", i);
    }
    fclose(txt);

    // 開始測試數據, 分批測試
    TIME_PRINT({
        test_fgetc();
    });

    TIME_PRINT({
        test_fread();
    });

    return 0;
}

//
// 測試 fgetc 性能
//
void 
test_fgetc(void) {
    FILE * txt = fopen(_STR_DATA, "r");
    if (NULL == txt) {
        fprintf(stderr, "test_fgetc fopen w " _STR_DATA " error!\n");
        return;
    }

    size_t cnt = 0;
    int c;

    while ((c = fgetc(txt)) != EOF)
        ++cnt;

    fclose(txt);

    printf("test_fgetc cnt = %d\n", cnt);
}

// 
// 測試 fread 性能
//
void test_fread(void) {
    FILE * txt = fopen(_STR_DATA, "r");
    if (NULL == txt) {
        fprintf(stderr, "test_fread fopen w " _STR_DATA " error!\n");
        return;
    }

    size_t cnt = 0;
    char buf[BUFSIZ];

    for (;;) {
        int rn = fread(buf, sizeof(char), BUFSIZ, txt);
        // 存在信號中斷情況, 不考慮
        cnt += rn;
        if (rn < BUFSIZ)
            break;
    }

    fclose(txt);

    printf("test_fread cnt = %d\n", cnt);
}
View Code

實驗二 fread最優解

  這裡測試的主要思路是基於fread設置不同的緩沖區, 開始測試性能對比情況. 首先test_file_define.c

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define _STR_DATA    "data.txt"
#define _INT_DATA    (1024*1024*128)
#define _INT_SZS    (64)
#define _INT_SZE    (4096)

// 測試 fread 性能
void test_fread(int sz);

//
// 測試C大文件處理方式
//
int main(int argc, char * argv[]) {

    // 先構建測試環境
    FILE * txt = fopen(_STR_DATA, "r");
    if (NULL == txt) {
        txt = fopen(_STR_DATA, "w");
        if (NULL == txt) {
            fprintf(stderr, "main fopen w " _STR_DATA " error!\n");
            exit(EXIT_FAILURE);
        }

        // 開始寫入數據
        for (int i = 0; i < _INT_DATA; ++i)
            fprintf(txt, "%d", i);
    }
    fclose(txt);

    // 開始測試數據, 分批測試
    for (int sz = _INT_SZS; sz <= _INT_SZE; sz <<= 1) {
        clock_t st, et;
        st = clock();
        test_fread(sz);
        et = clock();
        printf("sz = %6d => time:%lf seconds\n", sz, (0.0 + et - st) / CLOCKS_PER_SEC);
    }

    return 0;
}

// 
// 測試 fread 性能
//
void test_fread(int sz) {
    FILE * txt = fopen(_STR_DATA, "r");
    if (NULL == txt) {
        fprintf(stderr, "test_fread fopen w " _STR_DATA " error!\n");
        return;
    }

    size_t cnt = 0;
    char buf[_INT_SZE];

    for (;;) {
        int rn = fread(buf, sizeof(char), sz, txt);
        // 存在信號中斷情況, 不考慮
        cnt += rn;
        if (rn < sz)
            break;
    }

    fclose(txt);

    printf("test_fread cnt = %d\n", cnt);
}

最終測試結果如下

直接說我得到的結論是

  1). fread讀取的時候, buf 和 文件大小, 機器等共同影響了最優解

  2). 在設置緩沖大小為BUFSIZ左右, 性能都是可以接受的.

通過上面兩個實驗, 最終得到的一個結論. 處理大文件IO讀取時候, 設計緩沖區可以采用下面套路將會獲得更好的性能.

char buf[BUFSIZ];
size_t rn;

do {
       rn = fread(buf, sizeof(char), BUFSIZ, txt);
       if (rn < 0) {
             // 初始失敗的情況
             ....    
       }
       
       // 處理合法情況
      ....       

} while(rn == BUFSIZ); 

 

正文 - 構建一個成果

  到這裡, 通過上面結論, 開始構建一個成果, 例如讀取全部文件內容. 當然限定文件大小在100mb以內吧, 太大需要采用分量讀取算法了.

會使用到的輔助操作宏

// 
// 控制台輸出完整的消息提示信息, 其中fmt必須是 "" 包裹的字符串
// CERR            -> 簡單的消息打印
// CERR_EXIT    -> 輸出錯誤信息, 並推出當前進程
// CERR_IF        -> if語句檢查, 如果符合標准錯誤直接退出
// 
#ifndef _H_CERR
#define _H_CERR

#define CERR(fmt, ...) \
    fprintf(stderr, "[%s:%s:%d][errno %d:%s]" fmt "\n",\
        __FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__)

#define CERR_EXIT(fmt,...) \
    CERR(fmt, ##__VA_ARGS__), exit(EXIT_FAILURE)

#define CERR_IF(code) \
    if((code) < 0) \
        CERR_EXIT(#code)

#endif

首先聲明讀取文件的接口部分.

#ifndef _STRUCT_TSTR
#define _STRUCT_TSTR

struct tstr {
    char * str;            // 字符串實際保存的內容
    size_t len;            // 當前字符串長度
    size_t cap;            // 字符池大小
};

// 定義的字符串類型
typedef struct tstr * tstr_t;

#endif  // !_STRUCT_TSTR

//
// 簡單的文件讀取類,會讀取完畢這個文件內容返回,失敗返回NULL.
// path        : 文件路徑
// return    : 創建好的字符串內容, 返回NULL表示讀取失敗
//
extern tstr_t tstr_freadend(const char * path);

這麼設計方面, 後續操作, 讀取內容長度, 繼續添加內容方便些. 詳細的實現如下

//
// 簡單的文件讀取類,會讀取完畢這個文件內容返回,失敗返回NULL.
// path        : 文件路徑
// return    : 創建好的字符串內容, 返回NULL表示讀取失敗
//
tstr_t
tstr_freadend(const char * path) {
    tstr_t tstr;
    char buf[BUFSIZ];
    size_t rn;
    char * ctmp;
    FILE * txt = fopen(path, "r");
    if (NULL == txt) {
        CERR("tstr_freadend fopen r %s is error!", path);
        return NULL;
    }

    // 分配內存
    tstr = malloc(sizeof(struct tstr));
    if (NULL == tstr) {
        fclose(txt);
        CERR("tstr_freadend malloc is error! path = %s.", path);
        return NULL;
    }
    tstr->len = 0;
    tstr->cap = _INT_TSTRING;
    tstr->str = NULL;

    // 讀取文件內容
    do {
        rn = fread(buf, sizeof(char), BUFSIZ, txt);
        if (rn < 0) {
            CERR("tstr_freadend fread is error! path = %s. rn = %d.", path, rn);
            fclose(txt);
            free(tstr->str);
            free(tstr);
            return NULL;
        }

        // 構建數據
        if (tstr->cap < tstr->len + rn) {
            do
                tstr->cap <<= 1;
            while (tstr->cap < tstr->len + rn);
            ctmp = realloc(tstr->str, tstr->cap);
            if (NULL == ctmp) {
                CERR("tstr_freadend realloc is error! path = %s!", path);
                fclose(txt);
                free(tstr->str);
                free(tstr);
                return NULL;
            }
            tstr->str = ctmp;
        }

        // 開始拷貝數據
        memcpy(tstr->str + tstr->len, buf, rn);
        tstr->len += rn;
    } while (rn == BUFSIZ);

    fclose(txt);

    // 繼續構建數據, 最後一行補充一個\0
    if (tstr->cap < tstr->len + 1) {
        do
            tstr->cap <<= 1;
        while (tstr->cap < tstr->len + 1);
        ctmp = realloc(tstr->str, tstr->cap);
        if (NULL == ctmp) {
            CERR("tstr_freadend realloc is end error! path = %s!", path);
            free(tstr->str);
            free(tstr);
            return NULL;
        }
    }

    tstr->str[tstr->len] = '\0';
    tstr->len += 1;

    return tstr;
}

最終測試文件 file_test_build.c

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>

#ifndef _STRUCT_TSTR
#define _STRUCT_TSTR

struct tstr {
    char * str;            // 字符串實際保存的內容
    size_t len;            // 當前字符串長度
    size_t cap;            // 字符池大小
};

// 定義的字符串類型
typedef struct tstr * tstr_t;

#endif  // !_STRUCT_TSTR

// 
// 控制台輸出完整的消息提示信息, 其中fmt必須是 "" 包裹的字符串
// CERR            -> 簡單的消息打印
// CERR_EXIT    -> 輸出錯誤信息, 並推出當前進程
// CERR_IF        -> if語句檢查, 如果符合標准錯誤直接退出
// 
#ifndef _H_CERR
#define _H_CERR

#define CERR(fmt, ...) \
    fprintf(stderr, "[%s:%s:%d][errno %d:%s]" fmt "\n",\
        __FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__)

#define CERR_EXIT(fmt,...) \
    CERR(fmt, ##__VA_ARGS__), exit(EXIT_FAILURE)

#define CERR_IF(code) \
    if((code) < 0) \
        CERR_EXIT(#code)

#endif

//
// 簡單的文件讀取類,會讀取完畢這個文件內容返回,失敗返回NULL.
// path        : 文件路徑
// return    : 創建好的字符串內容, 返回NULL表示讀取失敗
//
extern tstr_t tstr_freadend(const char * path);

//
// 測試文件讀取, 推薦都是50mb以下文件好處理一點
//
int main(int argc, char * argv[]) {

    const char * path = "顧城 - 沒有名字的詩歌.txt";
    tstr_t str = tstr_freadend(path);
    if (NULL == str)
        CERR_EXIT("日狗嗎? 這都讀不出來`!");

    // 這裡打出數據
    printf("當前總字符數:%d, 當前容量:%d.\n\n", str->len, str->cap);
    puts(str->str);

    free(str->str);
    free(str);
    return 0;
}

// 文本字符串創建的初始化大小
#define _INT_TSTRING    (32)

//
// 簡單的文件讀取類,會讀取完畢這個文件內容返回,失敗返回NULL.
// path        : 文件路徑
// return    : 創建好的字符串內容, 返回NULL表示讀取失敗
//
tstr_t
tstr_freadend(const char * path) {
    tstr_t tstr;
    char buf[BUFSIZ];
    size_t rn;
    char * ctmp;
    FILE * txt = fopen(path, "r");
    if (NULL == txt) {
        CERR("tstr_freadend fopen r %s is error!", path);
        return NULL;
    }

    // 分配內存
    tstr = malloc(sizeof(struct tstr));
    if (NULL == tstr) {
        fclose(txt);
        CERR("tstr_freadend malloc is error! path = %s.", path);
        return NULL;
    }
    tstr->len = 0;
    tstr->cap = _INT_TSTRING;
    tstr->str = NULL;

    // 讀取文件內容
    do {
        rn = fread(buf, sizeof(char), BUFSIZ, txt);
        if (rn < 0) {
            CERR("tstr_freadend fread is error! path = %s. rn = %d.", path, rn);
            fclose(txt);
            free(tstr->str);
            free(tstr);
            return NULL;
        }

        // 構建數據
        if (tstr->cap < tstr->len + rn) {
            do
                tstr->cap <<= 1;
            while (tstr->cap < tstr->len + rn);
            ctmp = realloc(tstr->str, tstr->cap);
            if (NULL == ctmp) {
                CERR("tstr_freadend realloc is error! path = %s!", path);
                fclose(txt);
                free(tstr->str);
                free(tstr);
                return NULL;
            }
            tstr->str = ctmp;
        }

        // 開始拷貝數據
        memcpy(tstr->str + tstr->len, buf, rn);
        tstr->len += rn;
    } while (rn == BUFSIZ);

    fclose(txt);

    // 繼續構建數據, 最後一行補充一個\0
    if (tstr->cap < tstr->len + 1) {
        do
            tstr->cap <<= 1;
        while (tstr->cap < tstr->len + 1);
        ctmp = realloc(tstr->str, tstr->cap);
        if (NULL == ctmp) {
            CERR("tstr_freadend realloc is end error! path = %s!", path);
            free(tstr->str);
            free(tstr);
            return NULL;
        }
    }

    tstr->str[tstr->len] = '\0';
    tstr->len += 1;

    return tstr;
}
View Code

 

測試結果是通過的

到這裡基本上, 我們已經通過驗證,構建了最終的操作代碼, 歡迎嘗試用於性能提升上.

 

後記 - 扯淡以後

  錯誤是難免的, 歡迎指正, 交流提高. O(∩_∩)O哈哈~

  笑傲江湖曲  http://music.163.com/#/song?id=30031035

   

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved