1. 实现最小化的存储引擎接口
任务说明
实现一个最小化的存储引擎接口,支持数据的写入(put)、按键读取(get)和删除(delete),并保证数据持久化到磁盘文件。接口设计需满足后续案例扩展需求,但本案例仅实现核心功能,不涉及索引结构、事务或并发控制。
接口定义
// storage.h
#ifndef STORAGE_H
#define STORAGE_H
#include <stdint.h>
#include <stddef.h>
typedef struct storage storage_t;
// 打开或创建存储引擎(数据文件路径由 path 指定)
storage_t* storage_open(const char* path);
// 写入键值对(若键已存在则覆盖)
int storage_put(storage_t* db, const char* key, size_t key_len,
const char* value, size_t value_len);
// 读取键对应的值;成功返回0,键不存在返回1,错误返回-1
int storage_get(storage_t* db, const char* key, size_t key_len,
char* value_buf, size_t buf_size, size_t* value_len);
// 删除键;成功返回0,键不存在返回1,错误返回-1
int storage_delete(storage_t* db, const char* key, size_t key_len);
// 关闭存储引擎,刷盘并释放资源
void storage_close(storage_t* db);
#endif // STORAGE_H
实现代码
// storage.c
#include "storage.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct storage {
FILE* fp;
char* path;
};
// 每条记录格式:[key_len:4字节][value_len:4字节][key][value]
// 删除采用墓碑标记:value_len == 0xFFFFFFFF 表示该键已删除
static int write_record(FILE* fp, const char* key, size_t key_len,
const char* value, size_t value_len) {
uint32_t klen = (uint32_t)key_len;
uint32_t vlen = (uint32_t)value_len;
if (fwrite(&klen, sizeof(klen), 1, fp) != 1) return -1;
if (fwrite(&vlen, sizeof(vlen), 1, fp) != 1) return -1;
if (key_len > 0 && fwrite(key, 1, key_len, fp) != key_len) return -1;
if (value_len > 0 && fwrite(value, 1, value_len, fp) != value_len) return -1;
return 0;
}
storage_t* storage_open(const char* path) {
storage_t* db = (storage_t*)calloc(1, sizeof(storage_t));
if (!db) return NULL;
db->fp = fopen(path, "a+b");
if (!db->fp) { free(db); return NULL; }
db->path = strdup(path);
return db;
}
int storage_put(storage_t* db, const char* key, size_t key_len,
const char* value, size_t value_len) {
if (!db || !db->fp || !key) return -1;
// 先写墓碑删除旧记录(若存在),再追加新记录
if (storage_delete(db, key, key_len) == -1) return -1;
if (write_record(db->fp, key, key_len, value, value_len) != 0) return -1;
fflush(db->fp);
return 0;
}
int storage_get(storage_t* db, const char* key, size_t key_len,
char* value_buf, size_t buf_size, size_t* value_len) {
if (!db || !db->fp || !key) return -1;
fseek(db->fp, 0, SEEK_SET);
uint32_t klen, vlen;
int found = 1; // 1=不存在
while (fread(&klen, sizeof(klen), 1, db->fp) == 1) {
if (fread(&vlen, sizeof(vlen), 1, db->fp) != 1) return -1;
char* kbuf = (char*)malloc(klen);
if (!kbuf) return -1;
if (klen > 0 && fread(kbuf, 1, klen, db->fp) != klen) { free(kbuf); return -1; }
if (klen == key_len && memcmp(kbuf, key, key_len) == 0) {
free(kbuf);
if (vlen == 0xFFFFFFFF) { // 墓碑
found = 1;
} else {
if (vlen > buf_size) { found = -1; break; }
if (vlen > 0 && fread(value_buf, 1, vlen, db->fp) != vlen) return -1;
if (value_len) *value_len = vlen;
found = 0;
}
break;
}
free(kbuf);
// 跳过值数据
if (vlen != 0xFFFFFFFF && vlen > 0) {
if (fseek(db->fp, vlen, SEEK_CUR) != 0) return -1;
}
}
return found;
}
int storage_delete(storage_t* db, const char* key, size_t key_len) {
if (!db || !db->fp || !key) return -1;
size_t dummy;
char tmp[1];
int ret = storage_get(db, key, key_len, tmp, 0, &dummy);
if (ret == -1) return -1;
if (ret == 1) return 1; // 不存在
// 追加墓碑记录
uint32_t tombstone = 0xFFFFFFFF;
if (write_record(db->fp, key, key_len, NULL, 0) != 0) return -1;
// 修正墓碑的value_len字段
fseek(db->fp, -(long)(key_len + sizeof(uint32_t)), SEEK_END);
if (fwrite(&tombstone, sizeof(tombstone), 1, db->fp) != 1) return -1;
fflush(db->fp);
return 0;
}
void storage_close(storage_t* db) {
if (!db) return;
if (db->fp) { fflush(db->fp); fclose(db->fp); }
free(db->path);
free(db);
}
数据布局说明
文件记录格式(追加写):
+----------+----------+--------+--------+
| key_len | value_len| key | value |
| 4字节 | 4字节 | 变长 | 变长 |
+----------+----------+--------+--------+
删除标记: value_len = 0xFFFFFFFF(墓碑)
读取时从文件头顺序扫描,后写入的同键记录覆盖先前的。
测试程序
// test_storage.c
#include "storage.h"
#include <stdio.h>
#include <string.h>
int main() {
storage_t* db = storage_open("test.db");
if (!db) { perror("open"); return 1; }
// 写入
const char* k1 = "name";
const char* v1 = "Alice";
if (storage_put(db, k1, strlen(k1), v1, strlen(v1)) != 0) return 1;
const char* k2 = "age";
const char* v2 = "30";
if (storage_put(db, k2, strlen(k2), v2, strlen(v2)) != 0) return 1;
// 读取
char buf[128];
size_t len;
if (storage_get(db, k1, strlen(k1), buf, sizeof(buf), &len) == 0) {
printf("get(name) = %.*s\n", (int)len, buf);
}
if (storage_get(db, k2, strlen(k2), buf, sizeof(buf), &len) == 0) {
printf("get(age) = %.*s\n", (int)len, buf);
}
// 覆盖写
const char* v1_new = "Bob";
storage_put(db, k1, strlen(k1), v1_new, strlen(v1_new));
if (storage_get(db, k1, strlen(k1), buf, sizeof(buf), &len) == 0) {
printf("get(name) after overwrite = %.*s\n", (int)len, buf);
}
// 删除
storage_delete(db, k2, strlen(k2));
int ret = storage_get(db, k2, strlen(k2), buf, sizeof(buf), &len);
printf("get(age) after delete = %s\n", (ret == 1) ? "not found" : "error");
storage_close(db);
return 0;
}
编译与运行
gcc -Wall -o test_storage storage.c test_storage.c
./test_storage
验证方法
运行测试程序后,观察输出是否符合以下预期:
get(name) = Alice
get(age) = 30
get(name) after overwrite = Bob
get(age) after delete = not found
同时检查当前目录下生成了 test.db 文件,且文件大小大于0。再次运行 ./test_storage,程序仍能正确读取之前写入的数据(持久化验证)。若输出与预期一致,且第二次运行结果相同,则判定实现通过。