做团购网站需要什么,花瓣网 素材 图库,常州建站价格,wordpress 插件 浮动小人目录 前言一、设计思路二、文件存储格式三、数据库操作3.1. 数据库结构3.2. 数据库初始化3.3. 插入3.4. 删除3.5. 修改3.6. 查询3.7. 清空 四、示例代码 前言
之前介绍了C语言实现AVL树#xff0c; 本文是对AVL树的一个简单应用#xff0c;在资源偏紧张的硬件设备中可以使用… 目录 前言一、设计思路二、文件存储格式三、数据库操作3.1. 数据库结构3.2. 数据库初始化3.3. 插入3.4. 删除3.5. 修改3.6. 查询3.7. 清空 四、示例代码 前言
之前介绍了C语言实现AVL树 本文是对AVL树的一个简单应用在资源偏紧张的硬件设备中可以使用如资源足够还是建议使用sqlite。 先把仓库地址贴一下:https://gitee.com/sauryniu/file-database
一、设计思路 简单的实现文件数据库需要考虑两个方面一个是数据的持久存储另一个是数据的快速操作(增删改查)。关于持久存储我们可以把数据存储到文件中快速操作的话则需要借助算法实现这里选择在内存中建立AVL树相对二叉树来说性能比较均衡又不像其他树那样复杂。 二、文件存储格式 文件头文件数据库头部用户自定义大小和数据结构可用于保存版本信息之类的数据。数据块个数类型为int最大4字节取决于操作系统位数。数据块用户真正保存的数据内容自定义大小和数据结构但是需要统一。
三、数据库操作
3.1. 数据库结构 封装结构对象方便操作。 struct _file_db
{file_db_t* _this;void* _private_;/*
func: 添加元素到文件数据库中para: db : 文件数据库指针ele : 要被添加的元素指针return:int : 0 : 失败 0 成功note:ele 传入的建议是非指针变量的地址如果使用的是指向动态内存的指针则用完后需自行释放资源
*/int (*add)(file_db_t* db, void *ele);/*
func: 通过键值删除指定键值对应的元素para: db : 文件数据库指针key : 元素对应的键值return:int : 0 : 失败 0 成功note:none.
*/int (*del)(file_db_t* db, int key);/*
func: 根据键值编辑指定的元素para: db : 文件数据库指针key : 元素对应的键值ele 目标元素将替换给定键值所对应的元素的值return:int : 0 : 失败 0 成功note:ele 参数通过用户传入的获取键值的函数计算得到的键值需要和本函数传入键值 key 一致
*/ int (*edit)(file_db_t* db, int key, void *ele);/*
func: 根据键值查询文件数据库中的元素para: db : 文件数据库指针key : 元素的键值return:void* : NULL 查询失败 other 查询到的元素的指针note:none.
*/void* (*query)(file_db_t* db, int key);/*
func: 写入文件数据库的文件头para: db : 文件数据库指针head : 写入的文件头指针return:int : 0 : 失败 0 成功note:none.
*/int (*write_head)(file_db_t* db, void* head);/*
func: 读取文件数据库的文件头到指定内存中para: db : 文件数据库指针head : 读出的文件头指针return:int : 0 : 失败 0 成功note:none.
*/int (*read_head)(file_db_t* db, void* head);/*
func: 返回文件数据库中的元素个数para: db : 文件数据库指针return:int : 0 : 失败 other 元素个数note:none.
*/int (*size)(file_db_t* db);/*
func: 遍历文件数据库中的所有内容然后使用传入的函数指针对每个元素执行操作para: db : 文件数据库指针visit : 对元素操作的函数指针return:int : 0 : 失败 0 成功note:none.
*/int (*traverse)(file_db_t* db, void (*visit)(void*));/*
func: 清除文件数据库内容但是保存文件头para: db : 文件数据库指针return:int : -1 : 失败 0 成功note:【重要】此函数不能与 file_db_destory 同时使用
*/int (*clear)(file_db_t* db);/*
func: 并释放文件数据库相关动态内存para: db : 文件数据库指针return:int : -1 : 失败 0 成功note:【重要】此函数不能与 file_db_destory 同时使用
*/int (*free)(file_db_t* db);/*
func: 销毁数据库文件并释放相关动态内存para: db : 文件数据库指针return:int : -1 : 失败 0 成功note:【重要】此函数不能与 file_db_free 同时使用
*/int (*destory)(file_db_t* db);
};3.2. 数据库初始化 初始化数据库传入数据库路径以及其他相关信息如果文件不存在会创建 /*
func: 使用指定的文件初始化文件数据库para: path : 存放数据的文件所在路径 head_size : 头大小data_size : 数据块大小pf_hash_func 从数据块中获取key的哈希函数head 头数据return:file_db_t* : 文件数据库指针note:如果不再使用该数据库需要调用销毁函数释放内存【重要】保存的数据的数据类型大小必须是固定的
*/
file_db_t* file_db_init(const char* path, int head_size, int data_size, int (*pf_hash_func)(void *), void* head);3.3. 插入 插入数据块时会先查询数据库中key是否存在不存在才插入。插入时先把数据块写到文件尾部然后更新数据块数量最后把数据更新到AVL树中。 /*
func: 添加元素到文件数据库中para: db : 文件数据库指针ele : 要被添加的元素指针return:int : 0 : 失败 0 成功-4 内部错误-5 key已存在-6 内存不足-7 写入文件异常
note:ele 传入变量如果使用的是指向动态内存的指针则用完后需自行释放资源
*/
static int file_db_add(file_db_t* db, void* ele)
{file_db_private_t* _this get_private_member(db);if(NULL _this) return -4;if(NULL ! _this-m_tree-query_by_key(_this-m_tree-_this, _this-pf_get_ele_key(ele)))return -5;int res_code 0;file_db_record_t* record_data (file_db_record_t*)malloc(sizeof(file_db_record_t));if(NULL record_data)return -6;void* ele_memory malloc(_this-m_data_size);if(NULL ele_memory){free(record_data);return -6;}memcpy(ele_memory, ele, _this-m_data_size);pthread_mutex_lock(_this-m_file_db_mutex);FILE *db_fp fopen(_this-m_path, rb);if(NULL db_fp) {FILE_DB_LOG_DEBUG(open db file error);res_code -7;goto RUNTIME_ERROR;}fseek(db_fp, 0, SEEK_END);record_data-offset ftell(db_fp);record_data-db db;int write_len fwrite(ele_memory, 1, _this-m_data_size, db_fp);if(write_len ! _this-m_data_size) {FILE_DB_LOG_DEBUG(write ele error, write[%d] but [%d], _this-m_data_size, write_len);fclose(db_fp);res_code -7;goto RUNTIME_ERROR;}_this-m_data_cnt;FILE_DB_LOG_DEBUG(write cnt %d, key %d, _this-m_data_cnt, _this-pf_get_ele_key(ele));fseek(db_fp, _this-m_head_size, SEEK_SET);write_len fwrite(_this-m_data_cnt, 1, sizeof(int), db_fp);if(write_len ! sizeof(int)){FILE_DB_LOG_DEBUG(write cnt error);_this-m_data_cnt--;fclose(db_fp);truncate(_this-m_path, record_data-offset);res_code -9;goto RUNTIME_ERROR;}fflush(db_fp);fclose(db_fp);record_data-ele ele_memory;pthread_mutex_unlock(_this-m_file_db_mutex);return _this-m_tree-add(_this-m_tree-_this, (void *)record_data);RUNTIME_ERROR:free(ele_memory);free(record_data);pthread_mutex_unlock(_this-m_file_db_mutex);return res_code;
}3.4. 删除 删除数据库块时会先找到该数据块在文件中的位置把文件尾部的第一个数据块复制到待删除的数据块所在位置然后把数据块数量减1最后在AVL树中把该数据块删除。 /*
func: 通过键值删除指定键值对应的元素para: db : 文件数据库指针key : 元素对应的键值return:int : 0 : 失败 0 成功note:none.
*/
static int file_db_del(file_db_t* db, int key)
{file_db_private_t* _this get_private_member(db);if(NULL _this) {FILE_DB_LOG_DEBUG(Filedatabase error!);return -2;}file_db_record_t* record_data _this-m_tree-query_by_key(_this-m_tree-_this, key);if(NULL record_data){FILE_DB_LOG_DEBUG(No such element. Del fail!);return -3;}pthread_mutex_lock(_this-m_file_db_mutex);FILE* db_fp fopen(_this-m_path, rb);if(NULL db_fp){pthread_mutex_unlock(_this-m_file_db_mutex);return -4;}if(record_data-offset _this-m_head_size sizeof(int) _this-m_data_size * (_this-m_data_cnt - 1)){fseek(db_fp, 0, SEEK_END);fseek(db_fp, -_this-m_data_size, SEEK_END);void* buff malloc(_this-m_data_size);if(1 ! fread(buff, _this-m_data_size, 1, db_fp)){FILE_DB_LOG_DEBUG(Read tail element error! file position %ld, ftell(db_fp));free(buff);fclose(db_fp);pthread_mutex_unlock(_this-m_file_db_mutex);return -5;}fseek(db_fp, record_data-offset, SEEK_SET);if(1 ! fwrite(buff, _this-m_data_size, 1, db_fp)){FILE_DB_LOG_DEBUG(Write tail element error!);free(buff);fclose(db_fp);pthread_mutex_unlock(_this-m_file_db_mutex);return -6;}free(buff);}_this-m_data_cnt--;fseek(db_fp, _this-m_head_size, SEEK_SET);int write_len fwrite(_this-m_data_cnt, sizeof(int), 1, db_fp);if(write_len ! 1){FILE_DB_LOG_DEBUG(write cnt error);_this-m_data_cnt;fclose(db_fp);pthread_mutex_unlock(_this-m_file_db_mutex);return -7;}fclose(db_fp);truncate(_this-m_path, _this-m_head_size sizeof(int) _this-m_data_cnt * _this-m_data_size );pthread_mutex_unlock(_this-m_file_db_mutex);return _this-m_tree-del_node_by_key(_this-m_tree-_this, key);
}3.5. 修改 修改数据块需要传入数据块对应key值和数据块内容定位到文件中数据块位置后更新最后把AVL树中的数据更新一下就可以。 /*
func: 根据键值编辑指定的元素para: db : 文件数据库指针key : 元素对应的键值ele 目标元素将替换给定键值所对应的元素的值return:int : 0 : 失败 0 成功note:ele 参数通过用户传入的获取键值的函数计算得到的键值需要和本函数传入键值 key 一致
*/
static int file_db_edit(file_db_t* db, int key, void *ele)
{file_db_private_t* _this get_private_member(db);if(NULL _this || NULL ele) return -1;if(key ! _this-pf_get_ele_key(ele)){FILE_DB_LOG_DEBUG(Edit error, Key value and element do not match);return -1;}pthread_mutex_lock(_this-m_file_db_mutex);file_db_record_t* record_data (file_db_record_t*)(_this-m_tree-query_by_key(_this-m_tree-_this, key));if(NULL record_data){FILE_DB_LOG_DEBUG(Edit error, Query data error);pthread_mutex_unlock(_this-m_file_db_mutex);return -2;}memset(record_data-ele, 0, _this-m_data_size);memcpy(record_data-ele, ele, _this-m_data_size);FILE_DB_LOG_DEBUG(query key[%d], ele key[%d], get key[%d], key, _this-pf_get_ele_key(ele), _this-pf_get_ele_key(record_data-ele));FILE* db_fp fopen(_this-m_path, rb);if(NULL db_fp) {FILE_DB_LOG_DEBUG(Edit error, File error);pthread_mutex_unlock(_this-m_file_db_mutex);return -3;}fseek(db_fp, record_data-offset, SEEK_SET);if(fwrite(record_data-ele, _this-m_data_size, 1, db_fp) ! 1){fclose(db_fp);pthread_mutex_unlock(_this-m_file_db_mutex);FILE_DB_LOG_DEBUG(Edit element, write new error!);return -4;}fclose(db_fp);pthread_mutex_unlock(_this-m_file_db_mutex);return 0;
}
3.6. 查询 查询比较简单直接传入key在AVL树中查询即可。 /*
func: 根据键值查询文件数据库中的元素para: db : 文件数据库指针key : 元素的键值return:void* : NULL 查询失败 other 查询到的元素的指针note:none.
*/
static void* file_db_query(file_db_t* db, int key)
{file_db_private_t* _this get_private_member(db);if(NULL _this) {FILE_DB_LOG_DEBUG(_this is NULL);return NULL;}file_db_record_t* record_data (file_db_record_t*)(_this-m_tree-query_by_key(_this-m_tree-_this, key));if(NULL record_data) {FILE_DB_LOG_DEBUG(record_data is NULL);return NULL;}return record_data-ele;
}3.7. 清空 清空时直接把文件截断然后把数据块数量写入0最后清空所有AVL树节点即可。 /*
func: 清除文件数据库内容但是保存文件头para: db : 文件数据库指针return:int : -1 : 失败 0 成功note:【重要】此函数不能与 file_db_destory 同时使用
*/
static int file_db_clear(file_db_t* db)
{file_db_private_t* _this get_private_member(db);if(NULL _this) return -1;int cnt 0;pthread_mutex_lock(_this-m_file_db_mutex);FILE* db_fp fopen(_this-m_path, rb);if(NULL db_fp){pthread_mutex_unlock(_this-m_file_db_mutex);return -1;}fseek(db_fp, _this-m_head_size, SEEK_SET);if(1 ! fwrite(cnt, sizeof(int), 1, db_fp)){fclose(db_fp);pthread_mutex_unlock(_this-m_file_db_mutex);FILE_DB_LOG_DEBUG([file_db_clear] : write cnt error);return -2;} fclose(db_fp);truncate(_this-m_path, _this-m_head_size sizeof(int));pthread_mutex_unlock(_this-m_file_db_mutex);_this-m_data_cnt 0;_this-m_tree-clear_node(_this-m_tree-_this);return 0;
}四、示例代码 示例代码使用名称为 example.db 的文件作为数据库文件采用随机数的方式最大写入20个元素通常会小于20个因为会有一部分重复的然后进行删、改、查的操作。 #include stdio.h
#include string.h
#include stdlib.h
#include time.h
#include FileDatabase.h#define EXAMPLE_FILE_DB example.db
#define FILE_DB_SIZE 20
static file_db_t* file_db;typedef struct _db_head
{int version;char reserve[252];
}db_head_t;typedef struct _db_data
{int key;char value[60];
}db_data_t;static int get_key(void *ele)
{if(NULL ele){return -1;}db_data_t* record (db_data_t*)ele;return record-key;
}static void visit(void* ele)
{if(NULL ele) {printf(ele null!!!);}db_data_t* data (db_data_t*)ele;printf([visit] key[%d]-value[%s]\r\n, data-key, data-value);
}int main(void)
{db_head_t head;head.version 1;file_db file_db_init(EXAMPLE_FILE_DB, sizeof(db_head_t), sizeof(db_data_t), get_key, head);if(NULL file_db){printf(create db error\r\n);return -1;}db_data_t element;int cnt 0;srand(time(NULL));int key[FILE_DB_SIZE];int index 0;for(int i 0; i FILE_DB_SIZE; i){int num rand()%FILE_DB_SIZE;key[index] num;//printf(index :%d - key %d\r\n, index, key[index]);element.key num;memset(element.value, 0, 60);sprintf(element.value, element%d, num);if(0 file_db-add(file_db-_this, element)){cnt;}}printf(Add cnt %d, total %d\r\n, cnt, file_db-size(file_db-_this));int random_key rand()%FILE_DB_SIZE;printf(Del key[%d], res[%d]\r\n, key[random_key], file_db-del(file_db-_this, key[random_key]));random_key rand()%FILE_DB_SIZE;memset(element.value, 0, 60);sprintf(element.value, edit%d, key[random_key]);element.key key[random_key];printf(Edit key[%d], res[%d]\r\n, key[random_key], file_db-edit(file_db-_this, key[random_key], element));random_key rand()%FILE_DB_SIZE;db_data_t* ele_query (db_data_t*) file_db-query(file_db-_this, key[random_key]);if(NULL ele_query){printf(ele_query null\r\n);}else{printf(Query key[%d], value[%s]\r\n, ele_query-key, ele_query-value);}file_db-traverse(file_db-_this, visit);file_db-clear(file_db-_this);file_db-free(file_db-_this);//file_db-destory(file_db-_this);return 0;
}