C语言基于AVL树实现简单的文件数据库

目录

  • 前言
  • 一、设计思路
  • 二、文件存储格式
  • 三、数据库操作
    • 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/111261.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BAT030:按列表名单将路径a下的文件夹批量复制到路径b

引言&#xff1a;编写批处理程序&#xff0c;实现按列表名单将路径a下的文件夹批量复制到路径b。 一、新建Windows批处理文件 参考博客&#xff1a; CSDNhttps://mp.csdn.net/mp_blog/creation/editor/132137544 二、写入批处理代码 1.右键新建的批处理文件&#xff0c;点击…

Minio 文件上传(后端处理同文件判断,同一文件秒传)

记录minio 文件上传 MinIO提供多个语言版本SDK的支持&#xff0c;下边找到java版本的文档&#xff1a; 地址&#xff1a;https://docs.min.io/docs/java-client-quickstart-guide.html maven依赖如下&#xff1a; XML <dependency><groupId>io.minio</groupId…

Flask Web 安装bootstrap失败pip install bootstrap

失败原因&#xff1a;网速太慢了 把公共wifi换成手机热点&#xff0c;成功&#xff1a;&#xff09; &#x1f603; 更新&#xff1a;开了手机热点还是报下面的错&#xff0c;但是把科学上网关了&#xff0c;就成功了&#xff0c;反正就是网络问题

基于nodejs+vue 衣服穿搭推荐系统

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

分布式链路追踪系统Skywalking的部署和应用

一&#xff0c;背景 随着业务的扩张, 系统变得越来越复杂, 由前端、app、api,微服务,数据库,缓存,消息队列,关系数据库, 列式数据库等构成了繁杂的分布式网络. 当出现一个调用失败的问题时,要定位异常在哪个服务,需要进入每一个服务里看日志, 这个过程的复杂度和工作量是不可想…

【XSS_MSN】基于GCN的web攻击Payload识别与可解释性分析

原文标题&#xff1a;Web Attack Payload Identification and Interpretability Analysis Based on Graph Convolutional Network 原文链接&#xff1a;https://ieeexplore.ieee.org/document/10076547/ 作者单位&#xff1a;四川大学网络安全学院 相似原理论文&#xff1…

字节码增强技术-ASM

概述 在Java中一般是用javac命令编译源代码为字节码文件&#xff0c;一个.java文件从编译到运行的示例如图所示: 使用字节码的好处&#xff1a;一处编译&#xff0c;到处运行。java 就是典型的使用字节码作为中间语言&#xff0c;在一个地方编译了源码&#xff0c;拿着.class …

TCP/IP模型五层协议

TCP/IP模型五层协议 认识协议 约定双方进行的一种约定 协议分层 降低了学习和维护的成本&#xff08;封装&#xff09;灵活的针对这里的某一层协议进行替换 四/五层协议 五层协议的作用 应用层 应用层常见协议 应用层常见协议概览 基于TCP的协议 HTTP&#xff08;超…

AI绘画的魅力与未来:人工智能如何重塑艺术创作

随着人工智能&#xff08;AI&#xff09;技术的不断进步&#xff0c;AI绘画已经成为艺术与技术交汇的新领域。通过深度学习、神经网络等先进技术&#xff0c;AI不仅能理解和模拟人类艺术家的创作风格&#xff0c;还能生成令人惊叹的原创艺术作品。本文旨在探讨AI绘画的现状、挑…

ChatGPT AIGC自动生成多条件复杂计算函数

在Excel中经常会遇到多条件判断,根据不同的条件与内容显示不同的值。 例如: 需要给每个员工根据入职年限,员工等级,满意度等维度给员工发年终奖。 这在职场办公过程中经常要面临的一个问题。如销售额达到多少,取多少提成,如学生成绩在什么区间是设置为优秀还是良好等一…

python 对图片增加边框,logo贴图,获取图片exif参数,填写图片文本内容

完整代码 # 找到个可以下载免费字体的网站https://font.chi删除我naz.com/mi删除我anfei.html from PIL import Image, ImageDraw, ImageFont import exifreaddef photo_exif(image_path):f open(image_path, rb)tags exifread.process_file(f)# 打印所有照片信息&#xff0…

2023CRM排行:深度对比16款CRM

客户关系管理系统&#xff08;CRM&#xff09;作为数字化转型的重要载体&#xff0c;选择一个优秀的CRM系统将为企业未来健康增长保障。市场上CRM软件众多&#xff0c;但很难分清哪个适合自己&#xff0c;最近赶在公司选型&#xff0c;我对市场所有软件进行了一个调研&#xff…

单点登录是什么?

单点登录&#xff08;Single Sign On, SSO&#xff09;是指在同一帐号平台下的多个应用系统中&#xff0c;用户只需登录一次&#xff0c;即可访问所有相互信任的应用系统。 单点登录的本质就是在多个应用系统中共享登录状态。如果用户的登录状态是记录在 Session 中的&#xff…

异或运算.

相同为0&#xff0c;不同为1。 1 ^ 10 0 ^ 00 1 ^ 01 0 ^ 11性质&#xff1a; 0 ^ N N N ^ N 0交换、结合 a ^ b b ^ a&#xff1b; (a ^ b) ^ c a ^ (b ^ c)&#xff1b; 因此异或全部的元素的结果就是那个只出现1次的元素。 实现两个值的交换&#xff0c;而不必使…

C的魅力在于指针

原有的adrv9025 代理框架很好用,在其原有的平台上做改进

Python特征分析重要性的常用方法

前言 特征重要性分析用于了解每个特征(变量或输入)对于做出预测的有用性或价值。目标是确定对模型输出影响最大的最重要的特征&#xff0c;它是机器学习中经常使用的一种方法。 为什么特征重要性分析很重要? 如果有一个包含数十个甚至数百个特征的数据集&#xff0c;每个特征…

C#冒泡排序算法

冒泡排序实现原理 冒泡排序是一种简单的排序算法&#xff0c;其原理如下&#xff1a; 从待排序的数组的第一个元素开始&#xff0c;依次比较相邻的两个元素。 如果前面的元素大于后面的元素&#xff08;升序排序&#xff09;&#xff0c;则交换这两个元素的位置&#xff0c;使…

汽车屏类产品(四):仪表Cluster

###前言 仪表Cluster/仪表盘Dashboard,作为伴随汽车诞生就存在的一个主要零部件之一,从机械到电子到数字,可以说也是逐渐发展到现在的。 目前的主流框图如下,中间processor就是主控芯片,可能有buttons/switches,有display显示屏+backlight背光,有audio->speake…

线性代数-Python-01:向量的基本运算 -手写Vector -学习numpy的基本用法

文章目录 代码目录结构Vector.py_globals.pymain_vector.pymain_numpy_vector.py 一、创建属于自己的向量1.1 在控制台测试__repr__和__str__方法1.2 创建实例测试代码 二、向量的基本运算2.1 加法2.2 数量乘法2.3 向量运算的基本性质2.4 零向量2.5 向量的长度2.6 单位向量2.7 …

mybatis自定义类型控制器(TypeHandler)处理将字符串处理为集合

1. 问题&#xff1a; 假设这么一个场景 localurl里面的值大概这样&#xff1a;dwad21.jpg,dwad22.jpg,dwad.23.jpg 是一个字符串 如果我在sql表中有一个字段&#xff08;local_url&#xff09;是本地图片资源的多个url字符串拼接值。我想在java后端中不进行额外的转换就取值加…