package com.github.darlinglele.bitcask; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.util.HashMap; public class BitCask<T> { private final Indexer indexer; private final String name; private static HashMap<String, BitCask> bitCasks = new HashMap<>(); private long offset; /** * 操作对应索引名称的工具类,比如可以put,get * @param name 索引名称 * 用索引名称来标记不同的索引集合. 比如Student表示学生索引,里面存放的是所有学生的索引 * 可以再加上Teacher的索引,存放的是Teacher的索引 * @param <T> 泛型类 * @return */ public static <T> BitCask<T> of(String name) { return of(name, defaultIndexOf(name)); } /** * @param name 索引名称 * @param indexFile 索引文件名, 内存中的索引信息需要持久化到磁盘.注意不是数据内容的磁盘文件 * @param <T> * @return */ public static <T> BitCask<T> of(String name, String indexFile) { // bitCasks维护了索引名称和对应的BitCask, 如果索引名称已经存在, 直接从map中获取 if (bitCasks.containsKey(name)) { return (BitCask<T>) bitCasks.get(name); } else { BitCask<T> newBitCask = new BitCask<>(name, indexFile); bitCasks.put(name, newBitCask); return newBitCask; } } private static String defaultIndexOf(String name) { return name + ".index"; } /** * 根据索引名称和索引文件构造BitCask * @param name 索引名称 * @param indexFile 索引文件 */ private BitCask(String name, String indexFile) { this.name = name; this.indexer = loadIndexFrom(indexFile); } // read index-file to memory private Indexer loadIndexFrom(String indexFile) { RandomAccessFile file = getFileAccesser(indexFile); try { // 如果文件内容为空,创建一个Indexer: 实际上是创建一个Map if (file.length() == 0) { return new Indexer(); } } catch (IOException e) { e.printStackTrace(); } byte[] bytes = null; try { // 文件内容不为空,读取文件内容,转化为字节数组 bytes = readBytesFromFile(0, (int) file.length(), indexFile); } catch (IOException e) { e.printStackTrace(); } finally { if (file != null) { try { file.close(); } catch (IOException e) { e.printStackTrace(); } } } if (bytes == null) { return new Indexer(); } // 将字节数组反序列化为对象 Object object = convertBytesToObject(bytes); // 转化为Indexer. Indexer是什么东东? 见updateIndex--> if (!(object instanceof Indexer)) return new Indexer(); else return (Indexer) object; } // write memory to index-file public void dumpIndexTo(String indexFile) { RandomAccessFile file = getFileAccesser(indexFile); if (file != null) { try { file.write(convertObjectToBytes(this.indexer)); } catch (IOException e) { e.printStackTrace(); } finally { if (file != null) { try { file.close(); } catch (IOException e) { e.printStackTrace(); } } } } } /** * 写数据 * 1. 序列化value * 2. 追加value到磁盘文件中 * 3. 更新内存中的索引 * @param key * @param value */ public void put(String key, T value) { // 保存数据时, 先序列化数据 byte[] bytes = convertObjectToBytes(value); // 写数据, 直接追加 if (appendValue(key, bytes)) { // 更新内存中的索引信息 updateIndex(key, bytes, this.offset); } } private boolean appendValue(String key, byte[] bytes) { // 数据会写到磁盘文件中, 文件名称为索引名称. 比如索引名称为Student的磁盘文件为Student return appendBytesToFile(bytes, this.name); } private boolean appendBytesToFile(byte[] bytes, String name) { RandomAccessFile file = getFileAccesser(name); try { // 写数据时,首先定位到文件末尾 long offset = file.length(); // 跳转到文件末尾 file.seek(offset); // 写入二进制数据 file.write(bytes); // 保存offset变量, 用于更新内存中的索引信息 this.offset = offset; return true; } catch (IOException e) { e.printStackTrace(); } finally { if (file != null) { try { file.close(); } catch (IOException e) { e.printStackTrace(); } } } return false; } /** * 更新索引: 索引的内容是Index对象. 包含了key的名称, 文件名, 在文件中的偏移量, 写入的数据大小 * * indexer是个内存中的Map对象. key是写入的key, value是Index对象. * Index对象维护了内存中的key索引:value在磁盘文件中的位置和大小 * @param key 数据的key * @param bytes 数据内容 * @param offset 数据写到磁盘文件中的偏移量/开始位置 */ private void updateIndex(String key, byte[] bytes, long offset) { this.indexer.put(key, new Index(key, this.name, offset, bytes.length)); } // 读取key的内容. 根据key构造Index public T get(String key) { return readFromFile(this.indexer.get(key)); } /** * 根据Index对象, 读取key的内容. Index包含了key以及在文件中的起始位置和数据大小. * @param index * @return */ private T readFromFile(Index index) { byte[] bytes = readBytesFromFile(index.offset, index.size, index.fileName); Object object = convertBytesToObject(bytes); return (T) object; } /** * @param offset 在文件中的偏移量, 从这里开始读 * @param size 要读多少 * @param fileName 文件名 * @return 数据 */ private byte[] readBytesFromFile(long offset, int size, String fileName) { byte[] bytes = new byte[size]; RandomAccessFile file = getFileAccesser(fileName); try { file.seek(offset); file.read(bytes); } catch (IOException e) { e.printStackTrace(); bytes = null; } finally { if (file != null) { try { file.close(); } catch (IOException e) { e.printStackTrace(); } } } return bytes; } // 序列化 private byte[] convertObjectToBytes(Object value) { try { return Serialization.serialize(value); } catch (IOException e) { e.printStackTrace(); return null; } } // 反序列化 private Object convertBytesToObject(byte[] bytes) { try { return Serialization.deserialize(bytes); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } // 如果文件名不存在, 则新建文件, 如果文件已经存在,则直接获取文件 private RandomAccessFile getFileAccesser(String name) { try { return new RandomAccessFile(name, "rw"); } catch (FileNotFoundException e) { e.printStackTrace(); } return null; } }