• 一则表驱动法的应用实例


     

    1 需求场景

         考虑如下需求场景:

         终端按固定时间间隔(单位为分钟)生成诊断日志(格式为UserName-Status-yyyy-mm-dd-hh-mm.log),并上传至服务器。若终端与服务器的传输通道中断,则终端本地暂存最新的N个日志文件,即第(N+1)个周期生成的新日志将覆盖第1个周期的旧日志,以此类推。待传输恢复后,终端一次性上传该N个日志。

         本文主要讨论该需求中“最新日志覆盖最旧日志”的功能点。为突出层次,文中“覆盖”用先删除旧日志后创建新日志实现。实际中先删后建和先建后删各有优点,前者适于内存受限(如仅允许存在N个固定大小的日志),后者更为安全(避免创建新日志失败但已删除旧日志)。

         创建文件时调用现成库函数即可,而删除时需确定当前最旧的那个文件。下文将介绍几种删除判决的实现方式(时间复杂度依次降低)。

     

    2 实现思路

    2.1 比较文件的创建时间

         Linux系统中文件没有创建时间的概念,只有访问时间(atime)、修改时间(mtime)和状态改动时间(ctime)。因该需求中日志创建后立即写入内容,其后内容和状态(权限与属性等)均不再改变,故可用mtime或ctime表征创建时间。

         调用stat库函数即可获取日志的三种时间,比较各日志的mtime或ctime(整数)信息即可。

     1 #define FILE_NUM       (unsigned short)30 //允许共存的文件数目
     2 #define FILE_NAME_LEN  sizeof("%Log-Clover-65535.log")
     3 
     4 #include <sys/types.h>
     5 #include <sys/stat.h>
     6 #include <dirent.h>
     7 #include <errno.h>
     8 #define DIR_FILE_LEN   96 //目录和文件组成的绝对路径最大长度
     9 const char *gDirectoryName = "/sdb1/Clover/linux_test/log";
    10 
    11 
    12 int CreateLogFile1(const char *pszCreateFileName){//比较文件时间戳
    13     DIR *pDir = opendir(gDirectoryName);
    14     if(NULL == pDir){
    15        fprintf(stderr,"Cannot open directory: %s!
    ", gDirectoryName);
    16        return -1;
    17     }
    18 
    19     unsigned char ucFileNum = 0;
    20     time_t tFileTempCtime = 0;
    21     char szDelFileName[NAME_MAX+1] = {0};   //待删除的文件名
    22     struct dirent *pFileEntry = NULL;
    23     while((pFileEntry = readdir(pDir)) != NULL){
    24         //剔除当前目录.,上一级目录..及隐藏文件,避免死循环遍历目录
    25         if(0 == strncmp(pFileEntry->d_name, ".", 1))
    26             continue;
    27 
    28         struct stat tFileStatus;          //文件状态信息
    29         char szAbsFile[DIR_FILE_LEN] = {0};  //文件绝对路径
    30         sprintf(szAbsFile, "%s/%s", gDirectoryName, pFileEntry->d_name);
    31         if(stat(szAbsFile, &tFileStatus) != 0){
    32             fprintf(stderr,"Call stat error(errno:%d)!
    ", errno);
    33             return -1;
    34         }
    35 
    36         if(S_ISDIR(tFileStatus.st_mode)) //跳过子目录
    37             continue;
    38 
    39         if((ucFileNum < 1) ||
    40            (tFileStatus.st_ctime < tFileTempCtime)){ //首个文件或当前文件更老
    41             tFileTempCtime = tFileStatus.st_ctime;
    42             strcpy(szDelFileName, pFileEntry->d_name);
    43         }
    44         ucFileNum++;
    45         if(ucFileNum >= FILE_NUM){
    46             char szDeleteAbsFile[DIR_FILE_LEN] = {0};  //待删除的文件绝对路径
    47             sprintf(szDeleteAbsFile, "%s/%s", gDirectoryName, szDelFileName);
    48             if(0 != remove(szDeleteAbsFile)){
    49                 fprintf(stderr,"Fail to delete file: %s!
    ", szDeleteAbsFile);
    50                 return -1;
    51             }
    52         }
    53     }
    54 
    55     char szCreateAbsFile[DIR_FILE_LEN] = {0};  //待创建的文件绝对路径
    56     sprintf(szCreateAbsFile, "%s/%s", gDirectoryName, pszCreateFileName);
    57     FILE *pFile = fopen(szCreateAbsFile, "w+");
    58     if(NULL == pFile){
    59         fprintf(stderr,"Cannot open file: %s
    ", szCreateAbsFile);
    60         return -1;
    61     }
    62 
    63     fputs(szCreateAbsFile, pFile); //暂时写入绝对路径
    64     fclose(pFile);
    65     printf("Create file(%s) success!
    ", pszCreateFileName);
    66 
    67     closedir(pDir);
    68 
    69     return 0;
    70 }
    View Code

    2.2 比较文件名字符串

         因日志文件名包含时间信息,故可直接调用strcmp库函数比较日志文件名字符串。

     1 int CreateLogFile2(const char *pszCreateFileName){//比较文件名
     2     DIR *pDir = opendir(gDirectoryName);
     3     if(NULL == pDir){
     4        fprintf(stderr,"Cannot open directory: %s!
    ", gDirectoryName);
     5        return -1;
     6     }
     7 
     8     unsigned char ucFileNum = 0;
     9     char szDelFileName[NAME_MAX+1] = {0};   //待删除的文件名
    10     struct dirent *pFileEntry = NULL;
    11     while((pFileEntry = readdir(pDir)) != NULL){//readdir返回的文件顺序与文件名无关
    12         //剔除当前目录.,上一级目录..及隐藏文件,避免死循环遍历目录
    13         if(0 == strncmp(pFileEntry->d_name, ".", 1))
    14             continue;
    15 
    16         if(ucFileNum < 1)  //首个文件
    17             strcpy(szDelFileName, pFileEntry->d_name);
    18 
    19         if(strcmp(szDelFileName, pFileEntry->d_name) > 0)  //当前文件更老
    20             strcpy(szDelFileName, pFileEntry->d_name);
    21 
    22         ucFileNum++;
    23         if(ucFileNum >= FILE_NUM){
    24             char szDeleteAbsFile[DIR_FILE_LEN] = {0};  //待删除的文件绝对路径
    25             sprintf(szDeleteAbsFile, "%s/%s", gDirectoryName, szDelFileName);
    26             if(0 != remove(szDeleteAbsFile)){
    27                 fprintf(stderr,"Fail to delete file: %s!
    ", szDeleteAbsFile);
    28                 return -1;
    29             }
    30         }
    31     }
    32 
    33     char szCreateAbsFile[DIR_FILE_LEN] = {0};  //待创建的文件绝对路径
    34     sprintf(szCreateAbsFile, "%s/%s", gDirectoryName, pszCreateFileName);
    35     FILE *pFile = fopen(szCreateAbsFile, "w+");
    36     if(NULL == pFile){
    37         fprintf(stderr,"Cannot open file: %s
    ", szCreateAbsFile);
    38         return -1;
    39     }
    40 
    41     fputs(szCreateAbsFile, pFile); //暂时写入绝对路径
    42     fclose(pFile);
    43     printf("Create file(%s) success!
    ", pszCreateFileName);
    44 
    45     closedir(pDir);
    46 
    47     return 0;
    48 }
    View Code

    2.3 比较文件句柄

         创建日志时为其创建一个句柄(整数编号),比较句柄即可。为便于说明,定义如下信息:

    typedef struct{

        char szFileName[NAME_MAX+1]; //文件名称

        unsigned int dwFileHandler;      //文件句柄

    }FILE_INFO;

    static FILE_INFO gFileInfoMap[FILE_NUM] = {{{0}}};  //FILE_NUM为允许共存的文件数目

         全局信息表gFileInfoMap存储当前各日志文件的文件名及其句柄。每当创建一个新日志时,删除句柄最小的文件,并更新其对应的数组记录:文件名称改为新日志名,文件句柄值增加FILE_NUM。

         例如:

         {“log20130517”, 1};

         {“log20130518”, 2};

         {“log20130519”, 3};

         创建“log20130520”文件后,上面的结构体数组内容变为

         {“log20130520”, 4};

         {“log20130518”, 2};

         {“log20130519”, 3};

         下次再创建新文件时覆盖“log20130518”——哪怕新文件名为“log20130513”(比前两种方法更安全)!

         该方法无需关心文件的时间信息。这类似于新员工报道时分配的工号,工号数字的大小就代表入职的时间先后(无论工号是否包含入职精确时间)。

         因2.4节方法可视为本节的“升级版“,此处省略本节实现。

    2.4 表驱动法(无需比较)

         为便于说明,定义如下信息:

    //gFileNameMap按新旧次序环形记录FILE_NUM个文件的文件名信息

    static char gFileNameMap[FILE_NUM][FILE_NAME_LEN] = {{0}};

    //gLatestFileIndex记录gFileNameMap第一维最新文件的下标,

    //该下标+1并对FILE_NUM取余后对应的元素为最旧的文件,即

    //Oldest = (Latest + 1) mod FILE_NUM.

    static unsigned short gLatestFileIndex = FILE_NUM-1;

         该方法利用“环形存储时最新文件后必为最旧文件“这一特性。最新文件为数组最后一个元素时,取余操作将定位到数组最前端的最旧文件。

     1 int CreateLogFile3(const char *pszCreateFileName){//表驱动法(无需比较)
     2     DIR *pDir = opendir(gDirectoryName); //目录操作并非必要
     3     if(NULL == pDir){
     4        fprintf(stderr,"Cannot open directory: %s!
    ", gDirectoryName);
     5        return -1;
     6     }
     7 
     8     char szDeleteAbsFile[DIR_FILE_LEN] = {0};  //待删除的文件绝对路径
     9     unsigned short wOldestFileIndex = 0; //(gLatestFileIndex+1)%FILE_NUM;
    10     if(FILE_NUM != (gLatestFileIndex+1))
    11         wOldestFileIndex = gLatestFileIndex + 1;
    12     if('' != gFileNameMap[wOldestFileIndex][0]){//记录为空时remove会删除目录
    13         sprintf(szDeleteAbsFile, "%s/%s", gDirectoryName, gFileNameMap[wOldestFileIndex]);
    14         if(0 != remove(szDeleteAbsFile)){
    15             fprintf(stderr,"Fail to delete file: %s!
    ", szDeleteAbsFile);
    16             return -1;
    17         }
    18     }
    19 
    20     char szCreateAbsFile[DIR_FILE_LEN] = {0};  //待创建的文件绝对路径
    21     sprintf(szCreateAbsFile, "%s/%s", gDirectoryName, pszCreateFileName);
    22     FILE *pFile = fopen(szCreateAbsFile, "w+");
    23     if(NULL == pFile){
    24         fprintf(stderr,"Cannot open file: %s
    ", szCreateAbsFile);
    25         return -1;
    26     }
    27 
    28     fputs(szCreateAbsFile, pFile); //暂时写入绝对路径
    29     fclose(pFile);
    30     printf("Create file(%s) success!
    ", pszCreateFileName);
    31 
    32     gLatestFileIndex = wOldestFileIndex;
    33     strcpy(gFileNameMap[gLatestFileIndex], pszCreateFileName);
    34 
    35     closedir(pDir);
    36 
    37     return 0;
    38 }
    View Code

     

    3 耗时比较

        采用如下方法粗略统计除2.3节外的三种实现耗时(未考虑缓存、预热等因素):

     1 #include <sys/time.h>
     2 #define TIME_ELAPSED(codeToTime) do{ 
     3     struct timeval beginTime, endTime; 
     4     gettimeofday(&beginTime, NULL); 
     5     {codeToTime;} 
     6     gettimeofday(&endTime, NULL); 
     7     long secTime  = endTime.tv_sec - beginTime.tv_sec; 
     8     long usecTime = endTime.tv_usec - beginTime.tv_usec; 
     9     printf("[%s(%d)]Elapsed Time: SecTime = %lds, UsecTime = %ldus!
    ", __FUNCTION__, __LINE__, secTime, usecTime); 
    10 }while(0)
    11 
    12 #include <unistd.h>
    13 void CalcTime1(void){
    14     unsigned short wFileIdx = 0;
    15     for(; wFileIdx < 1; wFileIdx++){
    16         //time_t精度为秒,批量测试文件创建时需作秒级延迟;
    17         //但这样会影响计时统计,因为sleep会"淹没"CreateLogFile1(单次执行微妙级)
    18         //sleep(1);
    19         char szFileName[FILE_NAME_LEN] = {0};
    20         sprintf(szFileName, "Log-Clover-%05d.log", wFileIdx);
    21         CreateLogFile1(szFileName);
    22     }
    23 }
    24 void CalcTime2(void){
    25     unsigned short wFileIdx = 0;
    26     for(; wFileIdx < 1000; wFileIdx++){
    27         char szFileName[FILE_NAME_LEN] = {0};
    28         sprintf(szFileName, "Log-Clover-%05d.log", wFileIdx);
    29         CreateLogFile2(szFileName);
    30     }
    31 }
    32 void CalcTime3(void){
    33     unsigned short wFileIdx = 0;
    34     for(; wFileIdx < 1000; wFileIdx++){
    35         char szFileName[FILE_NAME_LEN] = {0};
    36         sprintf(szFileName, "Log-Clover-%05d.log", wFileIdx);
    37         CreateLogFile3(szFileName);
    38     }
    39 }
    40 
    41 int main(void){
    42     TIME_ELAPSED(CalcTime1());
    43     TIME_ELAPSED(CalcTime2());
    44     TIME_ELAPSED(CalcTime3());
    45     DIR *pDir = NULL;
    46     TIME_ELAPSED(pDir = opendir(gDirectoryName));
    47     TIME_ELAPSED(readdir(pDir));
    48     struct stat tFileStatus;          //文件状态信息
    49     char szAbsFile[DIR_FILE_LEN] = {0};  //文件绝对路径
    50     sprintf(szAbsFile, "%s/%s", gDirectoryName, "Log-Clover-00002.log");
    51     TIME_ELAPSED(stat(szAbsFile, &tFileStatus));
    52     TIME_ELAPSED(remove(szAbsFile));
    53     FILE *pFile = NULL;
    54     TIME_ELAPSED(pFile = fopen(szAbsFile, "a+"));
    55     TIME_ELAPSED(fputs(szAbsFile, pFile));
    56     TIME_ELAPSED(fclose(pFile));
    57     TIME_ELAPSED(closedir(pDir));
    58     TIME_ELAPSED(strcmp("Log-Clover-00000.log","Log-Clover-00001.log"));
    59 
    60     TIME_ELAPSED({int i=0;for(;i<10000;i++){strcmp("Log-00000","Log-00001");}});
    61     TIME_ELAPSED({int i=0;for(;i<10000;i++){int new=20;if(FILE_NUM!=new+1){int old=new+1;}}});
    62     TIME_ELAPSED(int new=20;if(FILE_NUM!=new+1){int old=new+1;});
    63 
    64     return 0;
    65 }
    View Code

        统计结果如下所示:

    对象

    条件

    耗时(us)

    2.1: CalcTime1()

    单次

    792

    2.2: CalcTime2()

    单次

    214

    2.2: CalcTime2()

    FILE_NUM=30,循环1000次

    274357

    2.4: CalcTime3()

    FILE_NUM=30,循环1000次

    77789

    opendir()

    单次

    81

    readdir()

    单次

    13

    stat()

    单次

    9

    remove()

    单次

    78

    fopen()

    单次

    33

    fputs()

    单次

    8

    fclose()

    单次

    39

    closedir()

    单次

    8

    strcmp()

    单次

    2

    strcmp()

    循环10000次

    27

    Oldest = (Latest + 1) mod FILE_NUM

    循环10000次

    36

    Oldest = (Latest + 1) mod FILE_NUM

    单次

    1

    注意:计时结果仅作粗略参考,且每次统计时结果可能略有不同。

        可见,文件操作函数(opendir、fopen等)的计时噪声会“淹没“比较代码,导致2.4节表驱动法相比其他方法效果不甚显著。同时考虑到表驱动法其实无需调用opendir/closedir函数,故执行效率可进一步提高。

     

    5 总结

         本文给出的表驱动法适用于如下场景:

         在按照时空顺序依次创建的若干对象中,查找符合指定时空规则的某个对象(如第N个最老对象),而不关心对象内部信息。

  • 相关阅读:
    html+css设计简单的博客首页
    js基础知识总结(一)
    css基础知识总结
    HTML基础知识
    链表常见题目--附具体分析和代码
    程序的内存分配
    python线程互斥锁递归锁死锁
    动态规划算法python实现
    ECMAScript 6 开篇准备
    弦生成器
  • 原文地址:https://www.cnblogs.com/clover-toeic/p/3737283.html
Copyright © 2020-2023  润新知