当前位置: 首页 > news >正文

C语言实现一个简易数据库

1 简易数据库介绍

数据库(Database)是按照特定结构组织、存储和管理数据的集合,在嵌入式软件开发中,如果不便引入专业的数据库,又想方便的对一些数据进行管理,如果数据量不是很多,可考虑自己实现一个简易的数据库来管理数据。

数据的存储载体,可保存在一个文件中,数据库的基本操作,就是增、删、改、查。

对于简单的数据库存储的数据形式,就是key-value这种类型,通过一个键值(key),来对应一个数据(value),另外,可以让value支持多种类型,在编程中,比较常用的类型是整型、浮点型和字符串类型。

2 代码实现

2.1 数据结构

数据的存储格式,是使用二进制文件存储,有利于节省空间和提高读写效率,每条记录包含键名、值类型、值长度和值数据。

支持的数据类型:int、float 和 string 三种基本类型。

数据类型,定义为一个ValueType的枚举。

数据的值,因为一个key只会有一种类型,可以定义一个Value类型的共用体类型,来保存数据。

对于数据库KVDB结构,需要有一个数据库的名字,以及对应的文件句柄(FILE *)。

数据中数据库中存储形式,每一行存储一条数据,数据的结构为FileRecord。

具体的代码如下。

2.1.1 数据库结构

// 定义值类型
typedef enum {TYPE_INT,TYPE_FLOAT,TYPE_STRING
} ValueType;// 定义值的联合体
typedef union {int int_val;float float_val;char* str_val;
} Value;// 定义键值对结构
typedef struct {char* key;      // keyValueType type; // 数据类型Value value;    // 数据的值
} KVNode;// 数据库结构
typedef struct {FILE* file;     // 文件句柄char* filename; // 文件名
} KVDB;

2.1.2 每条数据在文件中的结构

#define KEY_NAME_LEN   (64)
#define VALUE_DATA_LEN (128)// 每条记录在文件中的存储格式
typedef struct {char key[KEY_NAME_LEN];             // 键名,最长63个字符ValueType type;                     // 值类型int value_len;                      // 值的长度(主要用于字符串)unsigned char data[VALUE_DATA_LEN]; // 存储值的数据
} FileRecord;

2.2 访问接口

对于数据库,需要提供一些基本的接口:

  • 打开 / 关闭数据库
  • 插入 / 更新键值对(kvdb_set)
  • 查询键值对(kvdb_get)
  • 删除键值对(kvdb_delete)
  • 遍历所有键值对(kvdb_iterate)
// 打开数据库
KVDB* kvdb_open(const char* filename);// 关闭数据库
void kvdb_close(KVDB* db);// 插入或更新键值对
bool kvdb_set(KVDB* db, const char* key, ValueType type, Value value);// 获取键对应的值
bool kvdb_get(KVDB* db, const char* key, KVNode* result);// 删除键值对
bool kvdb_delete(KVDB* db, const char* key);// 遍历所有键值对
void kvdb_iterate(KVDB* db);

2.2.1 打开/关闭数据库

打开数据库,基本步骤:

  • 根据传入的db的文件名,先构建KVDB的信息
  • 然后尝试fopen打开db文件,如果不存在(第一次使用),则先创建空的db文件
  • 最后将KVDB返回,供后续使用
// 打开数据库(传入DB文件的名称)
KVDB* kvdb_open(const char* filename) 
{KVDB* db = (KVDB*)malloc(sizeof(KVDB));if (!db) {return NULL;}db->filename = (char*)malloc(strlen(filename) + 1);if (!db->filename) {free(db);return NULL;}strcpy(db->filename, filename);// 以读写方式打开文件,如果不存在则创建db->file = fopen(filename, "rb+");if (!db->file) {db->file = fopen(filename, "wb+");if (!db->file) {printf("[%s] db file:%s not exist, create fail\n", __func__, filename);free(db->filename);free(db);return NULL;}printf("[%s] db file:%s not exist, create ok\n", __func__, filename);}printf("[%s] db file:%s exist, open ok\n", __func__, filename);return db;
}

关闭数据库

// 关闭数据库
void kvdb_close(KVDB* db) 
{if (db) {if (db->file) {fclose(db->file); // 关闭文件 }free(db->filename); // 释放文件名free(db); // 释放KVDB结构}
}

2.2.2 插入/更新

对数据进行插入或更新,基本步骤:

  • 根据要插入或更新的key、type和value,构建一条FileRecord的信息
  • 根据KVDB,访问db文件中的每一行,匹配是否是已存在的key
    • 如果key已存在,则更新这条数据的值
    • 如果key不存在,则在文件最后插入这条数据
// 插入或更新键值对
bool kvdb_set(KVDB* db, const char *key, ValueType type, Value value) 
{if (!db || !db->file || !key) {printf("[%s] NULL ptr\n", __func__);return false;}// 检查键长度if (strlen(key) >= KEY_NAME_LEN) {printf("[%s] key:%s len > %d\n", __func__, key, KEY_NAME_LEN);return false;}FileRecord record;strcpy(record.key, key);record.type = type;memset(record.data, 0, sizeof(record.data));// 根据类型处理值switch (type) {case TYPE_INT:{record.value_len = sizeof(int);memcpy(record.data, &value.int_val, sizeof(int));break;}case TYPE_FLOAT:{record.value_len = sizeof(float);memcpy(record.data, &value.float_val, sizeof(float));break;}case TYPE_STRING:{if (!value.str_val) return false;record.value_len = strlen(value.str_val) + 1; // 包含终止符if (record.value_len > sizeof(record.data)) return false;strcpy((char*)record.data, value.str_val);break;}default:return false;}// 先尝试找到并替换现有记录fseek(db->file, 0, SEEK_SET);long pos;while ((pos = ftell(db->file)) != EOF) {FileRecord existing;if (fread(&existing, sizeof(FileRecord), 1, db->file) != 1) {break;}if (strcmp(existing.key, key) == 0) {// 找到相同的键,替换记录fseek(db->file, pos, SEEK_SET);if (fwrite(&record, sizeof(FileRecord), 1, db->file) == 1) {print_db_set_info(__func__, key, type, value, 0, 1);fflush(db->file);return true;}print_db_set_info(__func__, key, type, value, 0, 0);return false;}}// 如果没找到,则在文件末尾添加新记录fseek(db->file, 0, SEEK_END);if (fwrite(&record, sizeof(FileRecord), 1, db->file) == 1) {fflush(db->file);print_db_set_info(__func__, key, type, value, 1, 1);return true;}print_db_set_info(__func__, key, type, value, 1, 0);return false;
}

打印

void print_db_set_info(const char *func, const char *key, ValueType type, Value value, int is_insert, int is_success)
{char *state = is_success ? "ok" : "fail";char *insertOrUpdate = is_insert ? "insert" : "update";if (TYPE_INT == type){printf("[%s] %s key:%s value:%d [%s]\n", func, insertOrUpdate, key, value.int_val, state);}else if (TYPE_FLOAT == type){printf("[%s] %s key:%s value:%f [%s]\n", func, insertOrUpdate, key, value.float_val, state);}else if (TYPE_STRING == type){printf("[%s] %s key:%s value:%s [%s]\n", func, insertOrUpdate, key, value.str_val, state);}
}

2.2.3 查询

对数据的查询,基本步骤:

  • 根据KVDB,访问db文件中的每一行(先存到FileRecord中),匹配是否是已存在的key
    • 如果key存在,则将数据拷贝到KVNode类型的result中,并返回
    • 如果key不存在,则返回失败
// 获取键对应的值
bool kvdb_get(KVDB* db, const char* key, KVNode* result) 
{    if (!db || !db->file || !key || !result) {printf("[%s] NULL ptr\n", __func__);return false;}fseek(db->file, 0, SEEK_SET);FileRecord record;while (fread(&record, sizeof(FileRecord), 1, db->file) == 1) {// 找到了keyif (strcmp(record.key, key) == 0) {result->key = (char*)malloc(strlen(record.key) + 1);if (!result->key) {printf("[%s] db no key:%s\n", __func__, key);return false;}// 赋值result->keystrcpy(result->key, record.key);// 赋值result->typeresult->type = record.type;// 赋值result->valueswitch (record.type) {case TYPE_INT:{memcpy(&result->value.int_val, record.data, sizeof(int));break;}case TYPE_FLOAT:{memcpy(&result->value.float_val, record.data, sizeof(float));break;}case TYPE_STRING:{result->value.str_val = (char*)malloc(record.value_len);if (!result->value.str_val) {print_db_get_info(__func__, key, result, 0);free(result->key);return false;}strcpy(result->value.str_val, (char*)record.data);break;}default:{print_db_get_info(__func__, key, result, 0);free(result->key);return false;}}print_db_get_info(__func__, key, result, 1);return true;}}// 未找到键return false;
}

打印

void print_db_get_info(const char *func, const char *key, const KVNode* result, int is_success)
{if (is_success){if (TYPE_INT == result->type){printf("[%s] get key:%s value:%d [ok]\n", func, key, result->value.int_val);}else if (TYPE_FLOAT == result->type){printf("[%s] get key:%s value:%f [ok]\n", func, key, result->value.float_val);}else if (TYPE_STRING == result->type){printf("[%s] get key:%s value:%s [ok]\n", func, key, result->value.str_val);}}else{if (TYPE_INT == result->type){printf("[%s] get key:%s [fail]\n", func, key);}else if (TYPE_FLOAT == result->type){printf("[%s] get key:%s [fail]\n", func, key);}else if (TYPE_STRING == result->type){printf("[%s] get key:%s [fail]\n", func, key);}}
}

2.2.4 删除

对数据的删除,基本步骤:

  • 创建一个临时文件
  • 根据KVDB,访问db文件中的每一行(先存到FileRecord中),匹配是否是待删除的key
    • 如果不是待删除的,则将该行数据拷贝到临时文件
    • 如果是待删除的,则跳过
  • 所有数据行处理完后,删除原db文件
  • 将临时db文件的名称重命名为原db文件的名称,并重新打开db文件

// 删除键值对
bool kvdb_delete(KVDB* db, const char* key) 
{if (!db || !db->file || !key) {printf("[%s] NULL ptr\n", __func__);return false;}// 创建一个临时文件char temp_filename[256];sprintf(temp_filename, "%s.tmp", db->filename);FILE* temp_file = fopen(temp_filename, "wb+");if (!temp_file) {printf("[%s] fopen tmp file:%s fail\n", __func__, temp_filename);return false;}bool found = false;fseek(db->file, 0, SEEK_SET);FileRecord record;// 复制所有不匹配的记录到临时文件while (fread(&record, sizeof(FileRecord), 1, db->file) == 1) {if (strcmp(record.key, key) != 0) {fwrite(&record, sizeof(FileRecord), 1, temp_file);} else {printf("[%s] delete key:%s\n", __func__, key);found = true;}}// 关闭文件fclose(db->file);fclose(temp_file);// 删除原文件,重命名临时文件printf("[%s] remove old db, then rename tmp db to norml db\n", __func__);remove(db->filename);rename(temp_filename, db->filename);// 重新打开数据库文件db->file = fopen(db->filename, "rb+");if (!db->file) {return false;}return found;
}

2.2.5 遍历数据库

为了便于观察db中保存了哪些数据,可以写一个函数,将db中的所有数据依次都打印出来。

// 遍历所有键值对
void kvdb_iterate(KVDB* db) 
{if (!db || !db->file) {return;}fseek(db->file, 0, SEEK_SET);FileRecord record;KVNode node;while (fread(&record, sizeof(FileRecord), 1, db->file) == 1) {node.key = record.key;node.type = record.type;switch (record.type) {case TYPE_INT:{memcpy(&node.value.int_val, record.data, sizeof(int));break;}case TYPE_FLOAT:{memcpy(&node.value.float_val, record.data, sizeof(float));break;}case TYPE_STRING:{node.value.str_val = (char*)record.data;break;}}print_node(&node);}
}

打印

// 打印键值对
void print_node(KVNode* node) 
{if (!node) {return;}printf("Key: %s,\t Type: ", node->key);switch (node->type) {case TYPE_INT:{printf("int,\t Value: %d\n", node->value.int_val);break;}case TYPE_FLOAT:{printf("float,\t Value: %.2f\n", node->value.float_val);break;}case TYPE_STRING:{printf("string,\t Value: %s\n", node->value.str_val);break;}default:printf("unknown\n");}
}

2.3 测试代码

可以写一个 测试代码来验证功能:

int main() 
{char *db_file = "test_kv.db";// 打开数据库KVDB* db = kvdb_open(db_file);if (!db) {printf("[%s] 无法打开数据库文件\n", __func__);return 1;}printf("[%s] open db file:%s ok\n", __func__, db_file);Value val;// 插入一些数据val.int_val = 20;kvdb_set(db, "count", TYPE_INT, val);val.float_val = 3.14159f;kvdb_set(db, "pi", TYPE_FLOAT, val);val.str_val = "25-09-13 13:48";kvdb_set(db, "time", TYPE_STRING, val);// 查询并打印数据KVNode result;if (kvdb_get(db, "count", &result)) {printf("[%s] 查询到 age: %d\n", __func__, result.value.int_val);free(result.key); // 释放分配的内存}if (kvdb_get(db, "pi", &result)) {printf("[%s] 查询到 pi: %.2f\n", __func__, result.value.float_val);free(result.key);}if (kvdb_get(db, "time", &result)) {printf("[%s] 查询到 time: %s\n", __func__, result.value.str_val);free(result.value.str_val); // 释放字符串内存free(result.key);}// 更新数据val.int_val = 30;kvdb_set(db, "count", TYPE_INT, val);if (kvdb_get(db, "count", &result)) {printf("[%s] 更新后 count: %d\n", __func__, result.value.int_val);free(result.key);}// 遍历所有数据printf("\n所有数据:\n");kvdb_iterate(db);// 删除数据kvdb_delete(db, "pi");printf("\n删除 pi 后所有数据:\n");kvdb_iterate(db);// 关闭数据库kvdb_close(db);return 0;
}

3 运行测试

运行效果如下:

4 总结

本篇使用C语言实现了一个基础的key-value类型的简易数据库,支持int、float、string三种类型的数据存储,最后进行实际运行验证程序的运行效果。

http://www.dtcms.com/a/442945.html

相关文章:

  • Oracle OCP认证考试题目详解082系列第45题
  • 3D绘图与交互式工具结合:Plotly与Bokeh深度解析
  • Java要被python取代了?3个技术维度拆分分析
  • 【软考-分析】
  • 站群软件想在网上卖货需要怎么做
  • 水果成篮_优选算法(C++)滑动窗口
  • dw网站建设字体颜色做超市dm的网站
  • 网站seo查询工具wordpress小工具页脚
  • PostIn入门到实战(7) - 如何快速调试websocket接口
  • 网站建设需求公司内部调查福田深圳网站建设
  • 如果自己建立网站做甲方去哪个网站应聘
  • 重要数据、长期存储 | 为什么要用机械硬盘?
  • 做木业网站怎样起名电商平台推广员是做什么的
  • 浙江省住房建设厅网站首页公司做网站的费属于广告费么
  • 卖汽车的网站怎么做网站一年费用多少钱
  • 不需要证件做网站找别人做网站注意事项
  • 建设岗位考试网站写网页用什么软件
  • 某个网站访问慢的原因兄弟们拿走不谢
  • 网站死链处理东莞人才网站
  • 西宁网站建设服务公司百度智能建站系统
  • 宜春建设网站网站后台问题
  • 2018做网站还是app全能搜
  • wap 网站模板教做香肠的网站
  • 上海浦东网站设计公司外贸网站建设可以吗
  • Metaora DAMM
  • 开发网站的意义网站建设浙江公司
  • 数据库集群技术
  • 建网站建设网站外贸公司英文网站建设
  • 网站开发工具推荐色流网站如何做
  • 做移动网站点击软件宠物网站 模板