一则表驱动法的应用实例

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!\n", 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)!\n", 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!\n", 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\n", szCreateAbsFile);
60 return -1;
61 }
62
63 fputs(szCreateAbsFile, pFile); //暂时写入绝对路径
64 fclose(pFile);
65 printf("Create file(%s) success!\n", pszCreateFileName);
66
67 closedir(pDir);
68
69 return 0;
70 }

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!\n", 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!\n", 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\n", szCreateAbsFile);
38 return -1;
39 }
40
41 fputs(szCreateAbsFile, pFile); //暂时写入绝对路径
42 fclose(pFile);
43 printf("Create file(%s) success!\n", pszCreateFileName);
44
45 closedir(pDir);
46
47 return 0;
48 }

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!\n", 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(‘\0‘ != 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!\n", 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\n", szCreateAbsFile);
25 return -1;
26 }
27
28 fputs(szCreateAbsFile, pFile); //暂时写入绝对路径
29 fclose(pFile);
30 printf("Create file(%s) success!\n", pszCreateFileName);
31
32 gLatestFileIndex = wOldestFileIndex;
33 strcpy(gFileNameMap[gLatestFileIndex], pszCreateFileName);
34
35 closedir(pDir);
36
37 return 0;
38 }

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!\n", __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 }

统计结果如下所示:










































































对象

条件

耗时(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个最老对象),而不关心对象内部信息。

一则表驱动法的应用实例,布布扣,bubuko.com

时间: 2024-11-10 00:56:50

一则表驱动法的应用实例的相关文章

【转】 数据驱动编程之表驱动法

http://blog.csdn.net/chgaowei/article/details/6966857 本文示例代码采用的是c语言.之前介绍过数据驱动编程<什么是数据驱动编程>.里面介绍了一个简单的数据驱动手法.今天更进一步,介绍一个稍微复杂,更加实用的一点手法——表驱动法.关于表驱动法,在<unix编程艺术>中有提到,更详细的描述可以看一下<代码大全>,有一章专门进行描述(大概是第八章). 简单的表驱动:<什么是数据驱动编程>中有一个代码示例.它其实也

关于表驱动法的思考

目前在学习软件构造课程中表驱动法的内容,了解后觉得它在处理涉及多个if-else问题时十分有用,下面提供一些简单的我对表驱动法的理解以及其在java中应用的小例子. 表驱动法,顾名思义,是用查表方式来获取数据,涉及到了表的结构.表是一种在很多语言中常见的数据结构,比如在java中我们使用map键值对集合的形式来定义表.关于java中map的用法如下: 创建: Type map = new HashMap(); 删除: map.clear(); 添加键值对: map.set(key,value);

RDIFramework.NET 中多表关联查询分页实例

RDIFramework.NET 中多表关联查询分页实例 RDIFramework.NET,基于.NET的快速信息化系统开发.整合框架,给用户和开发者最佳的.Net框架部署方案.该框架以SOA范式作为指导思想,作为异质系统整合与互操作性.分布式应用提供了可行的解决方案. 分页非常的常见,基本任何项目都会涉及到分页,这没什么好谈的,大多数我们分页对单表的分页比较多,对多表的分页我们可以通过视图来实现,当然还有其他的方式,在这儿,我以一个实例展示下使用我们的RDIFramework.NET来实现多表

使用jquery实现的清空表单元素代码实例

使用jquery实现的清空表单元素代码实例:如果表单的元素较多的话,如果想情况以前填写的内容可能有点耗费体力,不够人性化,下面就介绍一下如何利用jquery代码实现快捷清除表单元素内容的功能,先看一段代码实例: $('#theform')[0].reset(); 很朋友可能认为上面的代码就完全实现我们的要求,其实这是错误的,reset()函数是重置的意思,也就是将表单元素的值重置为默认值而不是清空,如下面的文本框: <input type="text" value="蚂

编程学习——表驱动法

近来阅读<代码大全>中“表驱动法”这一章节,发现其编程的思想在C语言实际编程很有指导作用,就想着将“表驱动法”应用于实际项目中. 任务需求:函数在进行业务处理之前,需要对外部输入的数据类型(dataType),数据索引(dataIndex),数据长度(dataLen)进行正确性检查 如果按照if-else结构进行判断的话,代码可能如下所示: enum DATA_TYPE{APPLE=0,PEAR=1,BANANA=2}; enum DATA_LEN{APPLE_LEN=20,PEAR_LEN=

Compiler_词法分析_表驱动法

本文出自:http://blog.csdn.net/svitter DFA: 使用了表驱动法: 构造的表如下: 表驱动 num . E +/- other 0 1 6 - - - 1 1 2 5 - - 2 2 - 3 - - 3 - - - 4 -- 4 5 - - - - 5 5 - - - - 6 2 - - - - 7   代码如下: //===========================================================================

【干货】Laravel --Validate (表单验证) 使用实例

前言 : Laravel 提供了多种方法来验证应用输入数据.默认情况下,Laravel 的控制器基类使用ValidatesRequests trait,该trait提供了便利的方法通过各种功能强大的验证规则来验证输入的 HTTP 请求.要掌握 Laravel 强大的验证特性,让我们先看一个完整的验证表单并返回错误信息给用户的例子. 在这之前如果您是首次接触 Laravel 而且并不知道路由如何跳转到指定的控制器 可以查看博主的Restfulapi或者Laravel官网对路由的介绍,在这里就不做介

表驱动法 -《代码大全》读书笔记

表驱动法是一种编程模式,从表里面查找信息而不是使用逻辑语句(if-else-switch),当是很简单的情况时,用逻辑语句很简单,但如果逻辑很复杂,再使用逻辑语句就很麻烦了. 比如查找一年中每个月份的天数,如果用表驱动法,完全不需要写一堆if-else-语句,直接把每个月份的天数存到一个数组里就行了,取值的时候直接下标访问,最多针对二月判断一下闰年.这么算的话,平时用的的HashMap,SparseArray也可以算是表驱动 表里可以存数据,也可以存指令,或函数指针等都可以. 示例 看一个例子,

表驱动法——直接访问表示例1

<代码大全>看到"表驱动法"一章,以下是表驱动法的第一个方法--直接访问表 import java.util.Scanner; import java.util.Calendar; class DaysPerMonth { public static void main(String[] args) { System.out.println("输入年份:"); Scanner scan = new Scanner(System.in); String i