文本数据库的使用心得

文本数据库的使用心得

项目背景

在嵌入式编程中,有的时候应用程序需要读取配置文件以及一些日志文件,程序能够随时记录这些数据的变化,并且把他们存储到flash中。经常碰到的数据有下面几种:

  • 配置文件

    在嵌入式变成中,程序启动的时候往往需要进行配置,比如你应用程序的版本号、 服务器地址等,配置文件一般是key-value值。所以我一般都是采用ini文件来存储配置信息。解析ini文件网上有相应的方法,只需要dictionary.h、dictionary.c、iniparse.h以及iniparse.c文件添加到自己的工程目录中就可以,具体方法可以上网百度下。

  • 日志文件

    比如我在做一个电话记录的功能,此时你需要把之前的电话记录记下来,需要记录的项有:电话开始时间、电话号码、通话时间、电话归属地等。此时用上面所说的配置文件格式就不太合适了,一个电话一个section是不合适的。所以这个时候我们可以使用纯文本来记录通话记录。一个通话记录1行,这一行中每一个项目用空格隔开。在读取的时候,通过每一行的格式化读取,我们就可以把这个记录读取出来。

用到的知识以及解决问题的逻辑

所需要的知识点

经过思考,文本的存储本质上时文本的读写操作,所以可以转换为文件I/O的操作。由于我们这涉及到文本的操作,所以我们首选的是带缓冲的文件I/O操作,即fopen、fread、fgets等等标准函数,其实这些函数在实现上也是系统调用的,即这些标准的库(fopen、fread等)在实现上也是调用了open、read等API。

日志存储方案

日志文件读取

对一个日志记录,我们以一台新的手机设备进行类比,假设你现在有一个新的手机,插上了电话卡,你打开你的通话记录,此时你的通话记录是空的,这个时候你所做的操作是去查询电话记录。那么怎么查询呢?当然是去存储器中查询,也就是说此时你需要对磁盘文件进行写。下面这段代码是我读取安防报警txt中的安防报警记录。我们主要关注的是文件I/O的操作。

typedef struct
{
    unsigned long timestamp;    //报警时间
    GuardSensor sensor;         //报警传感器, 如烟雾
    GuardStatus status;       	//...
    GuardType type;             //...
    GuardState state;           //...
} GuardLog;

static GuardLog *GuardGetLog(int index)
{
	return &guardLogArray[index];
}

static void GuardReadLog()
{
	FILE *f = NULL;
    char buf[LENGTH] = {0};
    int i = 0, tmp_sensor = 0, tmp_status = 0;
    unsigned long tmp_time = 0;
    GuardLog* entry = NULL;

    f = fopen(GUARD_RECORD, "r");
    if(f != NULL) {
        while(fgets(buf, sizeof(buf), f)!=NULL) {
            sscanf(buf, "%lu %d %d", &tmp_time, &tmp_sensor, &tmp_status);
            entry = &guardLogArray[i];
            entry->timestamp = tmp_time;
            entry->sensor = tmp_sensor;
            entry->status = tmp_status;
            i++;
			memset(buf, 0, sizeof(buf));
        }
        if(i!=0) {
            guardLogCount = i;
        }
    }
    fclose(f);
}

我们主要分析GuardReadLog()函数中的fgets语句以及sscanf语句,这两个语句是这个函数的精华所在。下面的是对函数fgets函数的定义以及解释。我们的目的是去文件逐行读取。但是我们需要大概清楚一行的长度有多大,这样我们才能确定这个buf多大。而且需要注意的是我们在文件的每一行最后需要加上换行符。这样我们就能够逐行读取没然后进行sscanf操作。

#include 

char *fgets(char *buf, int size, FILE *stream);

//从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止。

sscanf是一个非常强大的函数,我们这里用到的是最基本也是最常用:用于格式化字符串输入。下面为这个函数的定义以及解释。在一行中的每个参数都用空格隔开,那么用sscanf读取的时候同样用空格可以把每个参数提取出来。然后存储到相应的数组成员中。

#include 

int sscanf(const char *str, const char *fromat, ...);

/*
str是我们需要被分解的字符串,其中format是被分解的格式,转换后的结果存在各个参数内,本文用到的空格分解字符串,如下面两行(带换行符),也是两个字符串。
关关雎鸠 123123123 0 在河之洲
窈窕淑女 124567812 1 君子好逑

*/

总结日志文件的读取:首先日志文件需要有个规范的格式,如每一行的各个参数用空格隔开,在每一行的最后需要一个换行符;之后使用fgets函数进行一行的读,然后使用sscanf函数进行行分解存储到对应的参数中。

日志增加

将参数赋值到结构体元素中,然后使用SaveGuardRecord()既可以对日志进行保存。

static void GuardWriteLog(GuardSensor sensor, GuardStatus status, unsigned long timestamp)
{
    GuardLog* entry = NULL;

    pthread_mutex_lock(&guardLogMutex);

    if (guardLogCount >= MAX_GUARD_LOG_COUNT)
        GuardDeleteLog(guardLogCount - 1);
	
    entry = &guardLogArray[guardLogCount];

    memset(entry, 0, sizeof(GuardLog));

    entry->timestamp = timestamp;
    entry->sensor = sensor;
    entry->status = status;

    guardLogCount++;
    assert(guardLogCount <= MAX_GUARD_LOG_COUNT);

    pthread_mutex_unlock(&guardLogMutex);
}
日志存储

日志的写入是在需要将对应的一行写到文件中,注意行的格式就行,这里我们使用fprintf进行格式化字符串。然后使用fclose将文件流写入磁盘。

static void SaveGuardRecord(void)
{
    FILE *f;
    int count=0, i=0;
    GuardLog* entry = NULL;

    f = fopen(GUARD_RECORD, "wb");		//这里一定只用到w,因为我们需要更新文本里面的内容
	if (!f)
    {
	    printf("cannot open guard record file: %s\n", GUARD_RECORD);
        return;
    }

    count=GuardGetLogCount();
    for(i = count-1; i>= 0; i--)
    {
        entry = GuardGetLog(i);
 
        fprintf(f, "%lu %d %d\n", entry->timestamp, entry->sensor, entry->status);
    }


    fclose(f);
}
删除日志

选定一条记录,然后删除。假设我们有10条(0-9)记录,然后删掉第5(4)条记录,删掉的数组为i = 10 - index(5) - 1 =4;所谓的删除其实是将这个数据从数组中拿掉,然后将下一个元素顶到上一个来。memmove和memcpy功能差不多,但是memmove能够赋值内容重复区域。如果下雨存入flash中,只需要调用上面介绍的SaveGuardRecord()函数即可。

void GuardDeleteLog(int index)
{
    int i, endIndex;

    if (guardLogCount == 0 || index >= guardLogCount)
        return;

    pthread_mutex_lock(&guardLogMutex);

    assert(guardLogCount <= MAX_GUARD_LOG_COUNT);
    assert(index < guardLogCount);

    i = guardLogCount - 1 - index;
    endIndex = guardLogCount - 1;

    assert(i <= endIndex);

    memmove(&guardLogArray[i], &guardLogArray[i + 1], sizeof(GuardLog) * (endIndex - i));

    guardLogCount--;
    assert(guardLogCount >= 0);

    pthread_mutex_unlock(&guardLogMutex);
}
清空日志

将count设置为0,然后使用void SaveGuardRecord(),将txt文件清空。

void GuardClearLog(void)
{
    guardLogCount = 0;
	
}

完整的程序代码可以到csdn下载guard_log.c

你可能感兴趣的:(linux,I/O操作)