Simple and Fast ⚡
SFDB 即 Simple File Database, 一个 简单 的文件型数据库,使用 简单,移植 简单,功能 简单,原理 简单,一切都很 简单,一切都很 快。适用于 固定长度 的 记录型 数据存储,类似于时序数据库,可用于存储历史记录、报警记录、日志等。
如果你的项目需要记录数据,且数据的长度或最大长度是固定的,已存入的数据不需要修改,同时需要快速清空和基于 记录条数 (从第几条开始,查多少条数据)的查询,而你的项目恰好有个文件系统,那么 SFDB 可能适合你。
特性
使用文件进行存储
简单的数据写入接口
到达上限时自动从头覆盖
支持顺序与倒序查询
支持从任意条数开始查询
支持清空(重置)数据库
使用篇
本篇基于 HMI-Board 来演示 SFDB 的使用。
创建工程
1. 使用 RT-Thread Studio 基于 HMI-Board 开发板创建一个项目。选项都保持默认即可。
2. 创建完成后打开 RT-Thread Settings,选中 Enable SDCARD filesystem 打开文件系统。
3. 在组件配置中打开 RTC 设备驱动,并使能 使用软件模拟RTC设备选项(提供给文件系统使用)。
4. 添加 SFDB 软件包。
5. 勾选 SFDB 配置项的 Use sfdb example 打开示例代码。
6. 全部配置完进行保存,当前软件包会就被应用到项目中。
编译及下载
在编译之前,我们需要修改一下例程的配置,来适配我们的文件系统目录。打开 packages/sfdb-v0.0.2/examples/rtthread/example.c,找到宏定义 TEST_FILE_PATH,将其修改为 /test.sdb。
此处需要注意,sfdb 在不存在当前文件时会自动创建,但如果路径中包含有多层目录,则这些文件夹必须存在,即 sfdb 不会自动创建文件夹。
修改完路径配置后即可编译代码。
控制台看到上图的信息即编译成功,此时进行程序烧录。
运行示例
程序下载完成后连接并打开终端,按下 TAB 键可以看到命令行中出现了两条 SFDB 的命令:
此时说明 SFDB 已经成功运行起来了。而这两条命令作用如下:
sfdb_test:启动 SFDB 写入测试,系统会持续写入到设定的上限值
sfdb_read:读取测试数据库内的数据。
写入测试
例程中设定的数据库存储上限为10000条,同时会写入10100条数据。接下来我们使用 sfdb_test 来启动写入测试:
可以看到日志中完整写入了10100条数据,而且每条数据的写入时间也都维持在 5-10 ms,并没有随着写入数量的增多而增大。
读取测试
sfdb_read命令的形式有两种:
sfdb_read 读取数据库基本信息
sfdb_read [offset] [number] [order(0:asc 1:dsc)] 根据order从offset中读取number条数据
1. 基本信息读取
基本信息不需要输入参数,直接 sfdb_read 即可:
可以看到,数据库的基本信息包括:
当前最新数据的索引(容量10000,写入10100,多出来的将会从最老的数据开始覆盖)
数据库中的总有效数据
每条数据的长度
这些数据与我们的配置和写入也是完全一致的,说明我们刚才数据库的创建及写入操作是成功的。
2. 数据读取
读取的 order 为读取顺序,其中0为从最老的数据开始读,1为从最新的数据开始读:
- 1. 正序读取(从旧到新)
- 2. 倒序读取(从新到旧)
- 3. 注意
在倒序读取模式下,由于保留了读取性能, buf 的数据仍然是顺序的,使用者需要手动处理 buf 中的数据顺序。当数据 1-100 依次存入时,若以倒序从 offset 为 0 的位置读取 10 条数据,存入 buf 中,buf 中的数据顺序为 91 92 93 94 95 96 97 98 99 100 ,而非 100 99 98 97 96 95 94 93 92 91。使用者可以在应用层可以参考例程通过索引倒转的形式实现数据顺序的倒转,如下:
1ret = sfdb_read(&sfdb, data_buf, data_sz, offset, number, order);
2 for (int i = 0; i < ret; i++) {
3 if (order == SFDB_READ_ASC) {
4 print_index = i;
5 } else {
6 print_index = ret - i - 1;
7 }
8 SF_LOG("%-5d:%s", offset + i + 1, (char *)&data_buf[print_index * sfdb.hdr.record_len]);
9 }
移植篇
如果需要在别的平台使用SFDB,移植起来也是非常方便。仅需参考 sfdb_port.c 实现 sfdb_fs_t 里面的文件操作接口,以及在 sfdb_port.h 中包含当前平台所需要的头文件并实现 SF_MEMCPY、SF_MEMSET、SF_LOG 宏定义即可。
接口结构如下:
1typedef struct _db_fs {
2 int (*op)(struct _sfdb *db, const char *path, int flags); // Open file
3 int (*cl)(void *fd); // Close file
4 int (*sy)(void *fd); // Sync file
5 size_t (*rd)(void *fd, void *buf, size_t len); // Read file
6 size_t (*wr)(void *fd, const void *buf, size_t len); // Write file
7 size_t (*sk)(void *fd, size_t offset); // Set file position
8 int (*rm)(const char *path); // Delete file
9} sfdb_fs_t;
RT-Thread 下的移植示例:
1// sfdb_port.c
2static int fs_open(sfdb_t *db, const char *path, int flags) {
3 int oflags = O_RDWR;
4 if (flags & SFDB_O_CREATE) oflags |= O_CREAT;
5 db->fd = (void *)open(path, oflags);
6 if ((int)db->fd < 0) {
7 return -1;
8 } else {
9 return 0;
10 }
11}
12static int fs_close(void *fd) {
13 if (fd >= 0) {
14 close((int)fd);
15 return 0;
16 } else {
17 SF_LOG("invalid fd %d, close failed", (int)fd);
18 return -1;
19 }
20}
21static int fs_sync(void *fd) { return fsync((int)fd); }
22static size_t fs_read(void *fd, void *buf, size_t len) { return read((int)fd, buf, len); }
23static size_t fs_write(void *fd, const void *buf, size_t len) { return write((int)fd, buf, len); }
24static size_t fs_seek(void *fd, size_t offset) {
25 int ret = 0;
26 ret = lseek((int)fd, offset, SEEK_SET);
27 if (ret < 0) return 0;
28 return ret;
29}
30static int fs_remove(const char *path) { return unlink(path); }
31sfdb_fs_t sfdb_fs = {
32 .op = fs_open,
33 .cl = fs_close,
34 .sy = fs_sync,
35 .rd = fs_read,
36 .wr = fs_write,
37 .sk = fs_seek,
38 .rm = fs_remove,
39};
1// sfdb_port.h
2#include
3#include
4#include
5#include
6#include
7#include
8#define SF_MEMCPY rt_memcpy
9#define SF_MEMSET rt_memset
10#define SF_LOG(format, ...) rt_kprintf("[SFDB]:" format "\r\n", ##__VA_ARGS__)
源码仓库
详细的API说明可以到源码的仓库查看:
Github: https://github.com/WKJay/sfdb
Gitee: https://gitee.com/wangjunjie997
———————End———————