Java项目实战-200行代码写简易KV数据库[附完整源码]
本文从0实现一个简易的KV数据库,代码行数不多,核心代码不超过200行。
设计思路
- 查询语法:支持String类型的Key和Value,暂不支持其他复杂类型,不支持SQL语法解析和执行计划优化。
- 存储引擎:基于顺序日志进行写入,每条执行命令的数据信息按行存在文本日志文件里。暂不支持类似hbase中的合并等复杂的逻辑。
代码模块
![](https://uploadfiles.nowcoder.com/files/20220505/163039247_1651750033888/1651740751385-36cd396c-741d-4a3f-890e-3c2f2594ae63.png)
下面给大家介绍核心代码解析。
核心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展示,还有相当多的优化工作需要做。
工程测试
测试比较简单,直接构建的实例,调用其查询、写入、删除方法即可。
运行日志:
完整代码和讲解: