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,程序仍能正确读取之前写入的数据(持久化验证)。若输出与预期一致,且第二次运行结果相同,则判定实现通过。

存储引擎接口调用时序 应用层 storage接口 磁盘文件 storage_put(key, value) 先查旧记录(墓碑) 追加写新记录 fwrite + fflush storage_get(key) 顺序扫描匹配key 返回最新值 应用调用 接口内部处理 文件I/O操作