Java项目实战-200行代码写简易KV数据库[附完整源码]

本文从0实现一个简易的KV数据库,代码行数不多,核心代码不超过200行。

设计思路

  • 查询语法:支持String类型的Key和Value,暂不支持其他复杂类型,不支持SQL语法解析和执行计划优化。
  • 存储引擎:基于顺序日志进行写入,每条执行命令的数据信息按行存在文本日志文件里。暂不支持类似hbase中的合并等复杂的逻辑。

代码模块

下面给大家介绍核心代码解析。

核心API定义:

/**
 * KV数据库操作Client
 *
 * @author summer
 * @version : SimpleKvClient.java, v 0.1 2022年05月05日 3:49 PM summer Exp $
 */
public interface SimpleKvClient {

    /**
     * 增加元素
     *
     * @param key k
     * @param value v
     */
    void put(String key, String value);

    /**
     * 获取指定key对于的值
     *
     * @param key k
     * @return
     */
    String get(String key);

    /**
     * 删除指定key
     *
     * @param key
     */
    void del(String key);
}

基于顺序日志的实现:

import com.alibaba.fastjson.JSON;
import com.summer.simplekv.api.SimpleKvClient;
import com.summer.simplekv.enums.CommandTypeEnum;
import com.summer.simplekv.model.CommandRequestModel;
import lombok.extern.java.Log;
import org.apache.commons.lang3.StringUtils;

import java.io.*;

/**
 * 基于日志顺序写入的KV数据库实现
 *
 * @author summer
 * @version : LogBasedKV.java, v 0.1 2022年05月05日 4:06 PM summer Exp $
 */
@Log
public class LogBasedKV implements SimpleKvClient {

    /**
     * 日志文件
     */
    private File logFile;

    /**
     * 构造函数
     *
     * @param fileName 文件名
     */
    public LogBasedKV(String fileName) {
        logFile = new File(fileName);
    }

    @Override
    public void put(String key, String value) {
        BufferedWriter bufferedWriter = null;
        try {
            FileWriter fileWriter = new FileWriter(logFile, true);
            bufferedWriter = new BufferedWriter(fileWriter);

            //日志写入内容构造
            CommandRequestModel commandRequestModel = new CommandRequestModel();
            commandRequestModel.setCommandTypeEnum(CommandTypeEnum.SET);
            commandRequestModel.setKey(key);
            commandRequestModel.setValue(value);

            //往日志文件中写入内容
            bufferedWriter.write(JSON.toJSONString(commandRequestModel));
            bufferedWriter.newLine();
        } catch (Exception e) {
            log.warning("put exception,[" + key + "," + value + "]");
        } finally {
            try {
                bufferedWriter.close();
            } catch (Exception e) {
                log.warning("bufferedWriter close exception");
            }
        }
    }

    @Override
    public String get(String key) {
        try {
            FileReader fileReader = new FileReader(logFile);
            BufferedReader bufferedReader = new BufferedReader(fileReader);

            //按行读取日志文件的内容,查找到最后一条修改类操作命令
            CommandRequestModel lastUpdateCommand = null;
            String line = bufferedReader.readLine();
            while (line != null) {
                CommandRequestModel commandRequestModel = JSON.parseObject(line, CommandRequestModel.class);
                if ((CommandTypeEnum.SET == commandRequestModel.getCommandTypeEnum()
                        || CommandTypeEnum.DEL == commandRequestModel.getCommandTypeEnum())
                    && StringUtils.equals(key, commandRequestModel.getKey())) {
                    lastUpdateCommand = commandRequestModel;
                }
                line = bufferedReader.readLine();
            }

            if (lastUpdateCommand == null || CommandTypeEnum.DEL == lastUpdateCommand.getCommandTypeEnum()) {
                return null;
            }

            return lastUpdateCommand.getValue();
        } catch (Exception e) {
            log.warning("get exception,[" + key + "]");
        }
        return null;
    }

    @Override
    public void del(String key) {
        BufferedWriter bufferedWriter = null;
        try {
            FileWriter fileWriter = new FileWriter(logFile, true);
            bufferedWriter = new BufferedWriter(fileWriter);

            //日志写入内容构造
            CommandRequestModel commandRequestModel = new CommandRequestModel();
            commandRequestModel.setCommandTypeEnum(CommandTypeEnum.DEL);
            commandRequestModel.setKey(key);

            //往日志文件中写入内容
            bufferedWriter.write(JSON.toJSONString(commandRequestModel));
            bufferedWriter.newLine();
        } catch (Exception e) {
            log.warning("del exception,[" + key + "]");
        } finally {
            try {
                bufferedWriter.close();
            } catch (Exception e) {
                log.warning("bufferedWriter close exception");
            }
        }
    }
}


性能分析

因为是追加写入,所以写入的性能非常快,比如Hbase就是采用的顺序写入的方式。关于读取,因为需要读取扫描整个文件来得到key对应的记录,因此性能较差,是O(N)。像实际生产环境使用,是会做一些优化工作的,比如把日志内容进行刷盘处理,同样的key的多条记录会进行合并处理,然后建立索引,查询性能会快很多。此处只是给大家做个demo展示,还有相当多的优化工作需要做。

工程测试

测试比较简单,直接构建的实例,调用其查询、写入、删除方法即可。

运行日志:

完整代码和讲解:

#面试##内推##春招##笔试题目##面经##Java#
全部评论
和DBCP等连接池设计思路基本一致,值得参考
点赞 回复 分享
发布于 2022-05-21 15:35

相关推荐

昨天 10:35
已编辑
西安科技大学 后端
点赞 评论 收藏
分享
评论
3
8
分享

创作者周榜

更多
牛客网
牛客企业服务