├── .gitignore ├── README.md ├── pom.xml ├── src ├── .DS_Store └── main │ ├── java │ └── org │ │ └── herDB │ │ ├── cache │ │ └── StorageCache.java │ │ ├── herdb │ │ ├── Configuration.java │ │ ├── HerDB.java │ │ └── UnsupportedException.java │ │ ├── index │ │ ├── BufferedBlock.java │ │ ├── IndexMemoryByte.java │ │ ├── IndexOutofRangeException.java │ │ ├── IndexSegment.java │ │ ├── ReadingBufferedBlock.java │ │ ├── Slot.java │ │ └── WritingBufferedBlock.java │ │ ├── net │ │ ├── Client.java │ │ ├── Commands.java │ │ └── Server.java │ │ ├── serializer │ │ ├── PrimitiveType.java │ │ ├── Reflection.java │ │ ├── Serializer.java │ │ ├── SerializerImp.java │ │ └── SerializerUtils.java │ │ ├── store │ │ ├── FSDirectory.java │ │ ├── InputOutData.java │ │ ├── Lock.java │ │ └── MMapInputStream.java │ │ └── utils │ │ ├── Bytes.java │ │ ├── Hash.java │ │ └── NumberPacker.java │ └── test │ ├── getTest.java │ └── putbyteTest.java └── target ├── classes └── org │ └── herDB │ ├── cache │ ├── StorageCache$1.class │ ├── StorageCache$NoCache.class │ └── StorageCache.class │ ├── herdb │ ├── Configuration.class │ ├── HerDB.class │ └── UnsupportedException.class │ ├── index │ ├── BufferedBlock.class │ ├── IndexMemoryByte.class │ ├── IndexOutofRangeException.class │ ├── IndexSegment.class │ ├── ReadingBufferedBlock.class │ ├── Slot.class │ └── WritingBufferedBlock.class │ ├── net │ ├── Client.class │ ├── Commands.class │ ├── Server$1.class │ ├── Server$Listenner.class │ └── Server.class │ ├── serializer │ ├── PrimitiveType$1.class │ ├── PrimitiveType$2.class │ ├── PrimitiveType$3.class │ ├── PrimitiveType$4.class │ ├── PrimitiveType$5.class │ ├── PrimitiveType$6.class │ ├── PrimitiveType.class │ ├── Reflection.class │ ├── Serializer.class │ ├── SerializerImp$1.class │ ├── SerializerImp$StringSerializer.class │ ├── SerializerImp.class │ └── SerializerUtils.class │ ├── store │ ├── FSDirectory$1.class │ ├── FSDirectory$FSDataStream.class │ ├── FSDirectory.class │ ├── InputOutData.class │ ├── Lock.class │ └── MMapInputStream.class │ └── utils │ ├── Bytes.class │ ├── Hash.class │ └── NumberPacker.class └── test-classes ├── getTest.class └── putbyteTest.class /.gitignore: -------------------------------------------------------------------------------- 1 | .metadata 2 | bin/ 3 | tmp/ 4 | *.tmp 5 | *.bak 6 | *.swp 7 | *~.nib 8 | local.properties 9 | .settings/ 10 | .loadpath 11 | .recommenders 12 | .classpath 13 | 14 | # Eclipse Core 15 | .project 16 | 17 | # External tool builders 18 | .externalToolBuilders/ 19 | 20 | # Locally stored "Eclipse launch configurations" 21 | *.launch 22 | /.DS_Store 23 | .idea/ 24 | *.iml 25 | /out 26 | /herDB 27 | /target -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # herDB 2 | 3 | > herDB是一个基于hash索引实现的key/value小型nosql,可以内嵌于java程序里。herDB存储的文件分为.index文件与.data文件; 4 | 存储的key/value数据都是基于二进制文件存储。 5 | 6 | > herDB's jar才不到40KB,实现简洁;支持并发操作,并且支持索引文件的扩容功能,get操作基本上一次磁盘随机读就能定位到数据。 7 | 8 | ## code samples 9 | 怎样创建herDB 10 | ``` java 11 | // "main.java.org.herDB.herdb"为目录名 12 | Configuration conf = Configuration.create("main.java.org.herDB.herdb"); 13 | 14 | // 初始的情况下,slots的数组的大小 15 | conf.set(Configuration.SLOTS_CAPACITY, "65536"); 16 | 17 | // 设置读写缓冲块的大小 18 | conf.set(Configuration.BUFFERED_BLOCK_SIZE, "8192"); 19 | 20 | // 设置分段segmentIndex数组的大小 21 | conf.set(Configuration.SEGMENTS_SIZE, "8"); 22 | 23 | // 设置key/value数据的最大长度 24 | conf.set(Configuration.ITEM_DATA_MAX_SIZE, "1024"); 25 | 26 | // 参数“main.java.org.herDB.herdb”是目录名 27 | HerDB main.java.org.herDB.herdb = HerDB.create(conf, "main.java.org.herDB.herdb"); 28 | 29 | main.java.org.herDB.herdb.put("china".getBytes(), "beijing".getBytes()); 30 | main.java.org.herDB.herdb.put("wuhan".getBytes(), "donghu".getBytes()); 31 | 32 | ...... 33 | 34 | main.java.org.herDB.herdb.commit(); 35 | ``` 36 | + 每次创建一个herDB,在完成相关的操作后最后都要调用commit()方法 37 | + 创建配置文件的时候可以不用调用set方法;`Configuration conf = Configuration.create("main.java.org.herDB.herdb")`直接得到一个默认配置的文件; 38 | 如能估计到数据体量时,可以先将`Configuration.SLOTS_CAPACITY`的属性设置的大些,这样子就可以减少`resize()`的次数,提高性能。 39 | 40 | 打开一个已有的herdb数据库,可以再进行put get操作: 41 | ``` java 42 | HerDB main.java.org.herDB.herdb = HerDB.open("main.java.org.herDB.herdb"); 43 | 44 | main.java.org.herDB.herdb.put("wuhan".getBytes(), "donghu".getBytes()); 45 | main.java.org.herDB.herdb.get("china".getBytes()) 46 | blablabla...... 47 | 48 | main.java.org.herDB.herdb.commit(); 49 | ``` 50 | 51 | 加快随机读的方法: 52 | ``` java 53 | // 只读模式下将herdb文件映射到内存加快随机读 54 | HerDB main.java.org.herDB.herdb = HerDB.openOnlyRead("main.java.org.herDB.herdb"); 55 | herDB.get("china".getBytes()) 56 | ``` 57 | 58 | ## 性能参数: 59 | 电脑性能: 60 | + 系统:OS XEI Capitan 61 | + 处理器: 2.6 GHz Intel Core i5 62 | + 硬盘: 1T的机械硬盘 63 | 添加数据的速度测试: 64 | ``` java 65 | Configuration conf = Configuration.create("main.java.org.herDB.herdb"); 66 | conf.set(Configuration.BUFFERED_BLOCK_SIZE, "4096"); 67 | try { 68 | HerDB main.java.org.herDB.herdb = HerDB.create(conf, "main.java.org.herDB.herdb"); 69 | long start = System.currentTimeMillis(); 70 | for(int i = 0; i < 10000000; i ++){ 71 | main.java.org.herDB.herdb.put(("key123"+ i).getBytes(), ("value案件司法就是发动机案说法jijaijdiajdifjaojfdiaodfijaosjdfoiajdfoiajfdi" 72 | + "ijaijsdfoiajodfjaojfiaoijdfoiajfidajfidojaoijdfiojfiajsidfjiasjdfijaidsfjaiojfiajdfidajsdifjaisdfa"+i).getBytes()); 73 | } 74 | System.out.println(System.currentTimeMillis() - start); 75 | main.java.org.herDB.herdb.commit(); 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | } 79 | ``` 80 | 一千万的数据追加在100s以内;索引占据的内存:100M左右; 81 | 82 | 随机读写的性能,针对上面的添加数据,在内存映射下只读模式测试: 83 | ``` java 84 | try { 85 | HerDB main.java.org.herDB.herdb = HerDB.openOnlyRead("main.java.org.herDB.herdb"); 86 | long start = System.currentTimeMillis(); 87 | for(int i = 0; i < 10000000; i ++){ 88 | main.java.org.herDB.herdb.get(("key123" + (int)(Math.random()* 10000000)).getBytes(), null); 89 | } 90 | System.out.println(System.currentTimeMillis() - start); 91 | main.java.org.herDB.herdb.commit(); 92 | } catch (Exception e) { 93 | e.printStackTrace(); 94 | } 95 | ``` 96 | 1000W次的随机读在10s以内; 97 | 98 | 99 | ## 实现细节: 100 | [herDB的实现概要](http://funeyu.github.io/2016/04/18/herDB%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A6%82%E8%A6%81/) 101 | 102 | ## TO DO: 103 | + ~~添加lru热缓存实现~~; 104 | + 提供数据压缩接口 105 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | HerDB 8 | HerDB 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | org.apache.maven.plugins 14 | maven-compiler-plugin 15 | 16 | 1.6 17 | 1.6 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | junit 26 | junit 27 | 4.12 28 | test 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/src/.DS_Store -------------------------------------------------------------------------------- /src/main/java/org/herDB/cache/StorageCache.java: -------------------------------------------------------------------------------- 1 | package org.herDB.cache; 2 | 3 | import org.herDB.herdb.Configuration; 4 | 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | 8 | 9 | 10 | public class StorageCache { 11 | 12 | private final LinkedHashMap cache; 13 | // 只记录key/value的长度,没有考虑LinkedHashMap每存一个数据占用的字节数 14 | private long cacheSize; 15 | private long currentSize; 16 | private final static float factor = 0.75F; 17 | 18 | 19 | /** 20 | * 用LinkedHashMap来做lru缓存,利用其removeEldestEntry函数 21 | * 22 | * @param cacheSize 为lru缓存的最大key/value字节数 23 | */ 24 | private StorageCache(final int cacheSize) { 25 | 26 | cache = new LinkedHashMap(cacheSize, factor, true) { 27 | @Override 28 | protected boolean removeEldestEntry(Map.Entry eldest) { 29 | 30 | boolean isFull = currentSize > cacheSize; 31 | if (isFull) { 32 | Object key = eldest.getKey(); 33 | Object value = eldest.getValue(); 34 | currentSize -= getSize(key) + getSize(value); 35 | } 36 | return isFull; 37 | } 38 | }; 39 | this.cacheSize = cacheSize; 40 | } 41 | 42 | private StorageCache() { 43 | cache = null; 44 | } 45 | 46 | public static StorageCache initCache(Configuration conf) { 47 | 48 | if (conf.isCacheOn() && conf.get(Configuration.STORAGE_CACHE_SIZE) > 0) { 49 | return new StorageCache(conf.get(Configuration.STORAGE_CACHE_SIZE)); 50 | } 51 | return new NoCache(); 52 | } 53 | 54 | public Object get(Object key) { 55 | 56 | return cache.get(key); 57 | } 58 | 59 | public void put(Object key, Object value) { 60 | 61 | currentSize += getSize(key) + getSize(value); 62 | cache.put(key, value); 63 | } 64 | 65 | public boolean contains(Object key) { 66 | 67 | return cache.containsKey(key); 68 | } 69 | 70 | private int getSize(Object obj) { 71 | 72 | if (obj == null) { 73 | return 0; 74 | } 75 | return ((byte[]) obj).length; 76 | } 77 | 78 | private static class NoCache extends StorageCache { 79 | 80 | NoCache() { 81 | 82 | } 83 | 84 | @Override 85 | public Object get(Object key) { 86 | return null; 87 | } 88 | 89 | @Override 90 | public void put(Object key, Object value) { 91 | 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/herdb/Configuration.java: -------------------------------------------------------------------------------- 1 | package org.herDB.herdb; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileWriter; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.util.HashMap; 10 | import java.util.Map.Entry; 11 | 12 | public class Configuration { 13 | 14 | // 读写缓冲块的大小 15 | public final static String BUFFERED_BLOCK_SIZE = "buffered.block.size"; 16 | // key/value 数据的最大长度 17 | public final static String ITEM_DATA_MAX_SIZE = "item.max.size"; 18 | // 初始化segment的slot的大小 19 | public final static String SLOTS_CAPACITY = "slots.capacity"; 20 | // 配置多少个分段 21 | public final static String SEGMENTS_SIZE = "segments.size"; 22 | // 配置lru缓存的内存大小 23 | public final static String STORAGE_CACHE_SIZE = "storage.main.java.org.herDB.cache.size"; 24 | // 存储配置信息 25 | public final static HashMap conf = new HashMap(); 26 | 27 | // 配置临时的'开关项'变量 28 | public final static String IS_ONLY_READ = "isOnlyRead"; 29 | public final static String IS_CACHE_ON = "isCacheOn"; 30 | public final static HashMap tem = new HashMap(); 31 | 32 | private final String dirPath; 33 | 34 | 35 | private Configuration(String dirPath) { 36 | 37 | this.dirPath = dirPath; 38 | 39 | // 先设置默认的配置,这些配置项要写入文件磁盘中 40 | set(Configuration.BUFFERED_BLOCK_SIZE, "32768"); 41 | set(Configuration.ITEM_DATA_MAX_SIZE, "1024"); 42 | set(Configuration.SLOTS_CAPACITY, "32768"); 43 | set(Configuration.SEGMENTS_SIZE, "8"); 44 | // 1kb的lru缓存大小 45 | set(Configuration.STORAGE_CACHE_SIZE, "1048576"); 46 | 47 | // 设置默认的临时开关项 48 | // 默认不打开热缓存开关 49 | tem.put(Configuration.IS_CACHE_ON, false); 50 | // 默认文件数据不是只读模式 51 | tem.put(Configuration.IS_ONLY_READ, false); 52 | } 53 | 54 | /** 55 | * 配置项key的修改 56 | * 57 | * @param key 58 | * @param value 59 | * @return 60 | */ 61 | public Configuration set(String key, String value) { 62 | 63 | conf.put(key, value); 64 | return this; 65 | } 66 | 67 | // 缓存打开返回 true 否则 false 68 | public boolean isCacheOn() { 69 | 70 | return tem.get(Configuration.IS_CACHE_ON); 71 | } 72 | 73 | // 是否是只读模式 74 | public boolean isOnlyRead() { 75 | 76 | return tem.get(Configuration.IS_ONLY_READ); 77 | } 78 | 79 | /** 80 | * 开关项的配置设定 81 | * 82 | * @param key 83 | * @param onOrOff true:打开 false:关闭 84 | */ 85 | public void setOnOff(String key, boolean onOrOff) { 86 | 87 | tem.put(key, onOrOff); 88 | } 89 | 90 | /** 91 | * 创建一个Configuration,默认的值 92 | * 93 | * @param dirPath db所在的目录 94 | * @return Configuration的实例 95 | */ 96 | public static Configuration create(String dirPath) { 97 | 98 | Configuration conf = new Configuration(dirPath); 99 | return conf; 100 | } 101 | 102 | /** 103 | * 打开一个已存在的Configuration 读取其中的内容 104 | * 105 | * @param dirPath configuration 所在的目录 106 | * @return Configuration的实例 107 | */ 108 | public static Configuration open(String dirPath) { 109 | 110 | Configuration conf = new Configuration(dirPath); 111 | try { 112 | conf.read(); 113 | } catch (IOException e) { 114 | e.printStackTrace(); 115 | } 116 | 117 | return conf; 118 | } 119 | 120 | /** 121 | * 配置项检查,检查通过了就写入磁盘文件中,否则报异常 122 | */ 123 | public void checkAndStore() { 124 | 125 | for (Entry entry : conf.entrySet()) { 126 | try { 127 | Integer.parseInt(entry.getValue()); 128 | } catch (NumberFormatException e) { 129 | throw new IllegalArgumentException("can not parse the string:" + entry.getValue() 130 | + "to int type"); 131 | } 132 | } 133 | 134 | // 要保证buffered.block.size > item.max.size * 2 135 | if (Integer.parseInt(conf.get("buffered.block.size")) < Integer.parseInt(conf.get("item.max.size")) * 2) { 136 | throw new IllegalArgumentException("buffered.block.size must be greater than item.max.sieze * 2"); 137 | } 138 | 139 | // 写入文件 140 | write(); 141 | } 142 | 143 | /** 144 | * 根据key获取配置项的value 145 | * 146 | * @param key 147 | * @return 148 | */ 149 | public int get(String key) { 150 | 151 | return Integer.parseInt(conf.get(key)); 152 | } 153 | 154 | // 将配置文件写入磁盘 155 | private void write() { 156 | 157 | try { 158 | FileWriter fWriter = new FileWriter(dirPath 159 | + "/herDB.conf", true); 160 | 161 | for (Entry entry : conf.entrySet()) { 162 | fWriter.write(entry.getKey() + ":" + entry.getValue()); 163 | // 换行 164 | fWriter.write(System.getProperty("line.separator")); 165 | } 166 | 167 | fWriter.close(); 168 | } catch (IOException e) { 169 | e.printStackTrace(); 170 | } 171 | } 172 | 173 | // 读取配置文件 174 | private void read() throws IOException { 175 | 176 | File file = new File(dirPath + "/herDB.conf"); 177 | FileInputStream fis = new FileInputStream(file); 178 | BufferedReader fReader = new BufferedReader(new InputStreamReader(fis)); 179 | 180 | String line; 181 | while ((line = fReader.readLine()) != null) { 182 | String[] results = line.split(":"); 183 | set(results[0], results[1]); 184 | } 185 | fReader.close(); 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/herdb/HerDB.java: -------------------------------------------------------------------------------- 1 | package org.herDB.herdb; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.Arrays; 6 | 7 | import org.herDB.cache.StorageCache; 8 | import org.herDB.index.IndexSegment; 9 | import org.herDB.serializer.SerializerImp; 10 | import org.herDB.store.FSDirectory; 11 | 12 | public final class HerDB { 13 | 14 | private Configuration conf; 15 | private IndexSegment[] segments; 16 | private FSDirectory fsd; 17 | private StorageCache cache; 18 | private static String DIRECTORY = "herDB"; 19 | private static String CONFIG = "herDB.conf"; 20 | 21 | private SerializerImp serilization = SerializerImp.build(); 22 | 23 | /** 24 | * HerDB的constructor,初始化IndexSegment数组 25 | * 26 | * @param conf 配置文件 27 | * @param fsd FSDirectory实例 28 | * @param isFirst 是否第一次创建 是:true, 不是:false(即为打开的操作) 29 | * @throws Exception 30 | */ 31 | private HerDB(Configuration conf, FSDirectory fsd, boolean isFirst) throws Exception { 32 | 33 | this.conf = conf; 34 | segments = new IndexSegment[conf.get(Configuration.SEGMENTS_SIZE)]; 35 | 36 | this.fsd = fsd; 37 | 38 | for (int i = 0, length = segments.length; i < length; i++) { 39 | segments[i] = IndexSegment.createIndex(fsd, "segment" + i, conf); 40 | } 41 | 42 | this.fsd = fsd; 43 | 44 | this.cache = StorageCache.initCache(conf); 45 | } 46 | 47 | /** 48 | * 新建一个HerDB实例 49 | * 50 | * @param dirPath 数据库所在的目录 51 | * @return HerDB的实例 52 | * @throws Exception 53 | */ 54 | public static HerDB create(Configuration conf, String dirPath) throws Exception { 55 | 56 | FSDirectory fsd = FSDirectory.create(dirPath, true); 57 | 58 | // 检查配置是否有问题 59 | conf.checkAndStore(); 60 | // 默认情况下打开lru缓存 61 | conf.setOnOff(Configuration.IS_CACHE_ON, true); 62 | 63 | HerDB herDB = new HerDB(conf, fsd, true); 64 | return herDB; 65 | } 66 | 67 | /** 68 | * 根据dirPath打开一个HerDB的数据库 69 | * 70 | * @param dirPath 71 | * @return 72 | * @throws Exception 73 | */ 74 | public static HerDB open(String dirPath) throws Exception { 75 | 76 | Configuration conf = Configuration.open(dirPath); 77 | //默认情况下打开lru缓存 78 | conf.setOnOff(Configuration.IS_CACHE_ON, true); 79 | FSDirectory fsd = FSDirectory.open(dirPath); 80 | 81 | return new HerDB(conf, fsd, false); 82 | } 83 | 84 | /** 85 | * 只打开一个现有的herDB数据内容,只进行读 86 | * 通过mmapfile 将文件映射到内存里来加快随机读 87 | * 88 | * @param dirPath HerDB的目录名称 89 | * @return 90 | * @throws Exception 91 | */ 92 | public static HerDB openOnlyRead(String dirPath) throws Exception { 93 | 94 | Configuration conf = Configuration.open(dirPath); 95 | // 默认情况下打开lru缓存 96 | conf.setOnOff(Configuration.IS_ONLY_READ, true); 97 | FSDirectory fsd = FSDirectory.open(dirPath); 98 | 99 | return new HerDB(conf, fsd, false); 100 | } 101 | 102 | /** 103 | * 打开热缓存 104 | * 105 | * @return 106 | */ 107 | public HerDB cacheOn() { 108 | 109 | conf.setOnOff(Configuration.IS_CACHE_ON, true); 110 | return this; 111 | } 112 | 113 | 114 | /** 115 | * 关闭热缓存 116 | * 117 | * @return 118 | */ 119 | public HerDB cacheOff() { 120 | 121 | conf.setOnOff(Configuration.IS_CACHE_ON, false); 122 | return this; 123 | } 124 | 125 | /** 126 | * 每进行完一系列数据库操作,最后都要commit(),将内存索引文件写入磁盘中 127 | */ 128 | public void commit() { 129 | 130 | for (int i = 0, length = segments.length; i < length; i++) { 131 | segments[i].commit(); 132 | } 133 | // 删除herdb.lock的锁文件 134 | fsd.releaseDir(); 135 | } 136 | 137 | public void put(String key, T value) { 138 | 139 | byte[] keyBytes = key.getBytes(); 140 | byte[] valueBytes = serilization.serialize(value); 141 | putInternal(keyBytes, valueBytes); 142 | } 143 | 144 | public T get(String key) { 145 | byte[] keyBytes = key.getBytes(); 146 | byte[] resultBytes = getInternal(keyBytes, null); 147 | if (resultBytes == null) 148 | return (T) null; 149 | return serilization.deserialize(resultBytes); 150 | } 151 | 152 | public void putBytes(String key, byte[] value) { 153 | 154 | byte[] keyBytes = key.getBytes(); 155 | putInternal(keyBytes, value); 156 | } 157 | 158 | public byte[] getBytes(String key) { 159 | 160 | byte[] keyBytes = key.getBytes(); 161 | return getInternal(keyBytes, null); 162 | } 163 | 164 | // HerDB的put操作原生字节序列操作,所有的添加都要经过这一步 165 | private void putInternal(byte[] key, byte[] value) { 166 | 167 | // 先加入lru缓存中 168 | try { 169 | segments[segmentFor(key)].put(key, value); 170 | } catch (IOException e) { 171 | e.printStackTrace(); 172 | } 173 | } 174 | 175 | /** 176 | * 通过key的字节数组去查询相应的value字节数组,若没查到则返回一默认值 177 | * 178 | * @param key 查询的key 179 | * @param value 没查到时返回的默认值 180 | * @return 181 | */ 182 | private byte[] getInternal(byte[] key, byte[] value) { 183 | 184 | byte[] results = null; 185 | 186 | //先查询lru缓存 187 | if ((results = (byte[]) cache.get(key)) != null) { 188 | return results; 189 | } 190 | 191 | // 添加到lru缓存 192 | if ((results = segments[segmentFor(key)].get(key)) != null) { 193 | cache.put(key, results); 194 | return results; 195 | } 196 | 197 | return value; 198 | } 199 | 200 | // 获取分段IndexSegment的索引值 201 | private int segmentFor(byte[] key) { 202 | 203 | return Arrays.hashCode(key) & conf.get(Configuration.SEGMENTS_SIZE) - 1; 204 | } 205 | 206 | // 判断herDB是否存在 207 | private static boolean isCreated() { 208 | File directory = new File(DIRECTORY); 209 | File f = new File(directory.getPath(), CONFIG); 210 | return f.exists() ? true : false; 211 | } 212 | /** 213 | * 创建一个HerDB实例 214 | * @param lruON 215 | * @param blockSize 216 | * @return 217 | */ 218 | public static HerDB bootStrap(Boolean lruON, int blockSize) throws Exception { 219 | if(isCreated()) { 220 | return HerDB.open(DIRECTORY); 221 | } 222 | 223 | Configuration conf = Configuration.create(DIRECTORY); 224 | conf.set(Configuration.BUFFERED_BLOCK_SIZE, blockSize + ""); 225 | conf.setOnOff(Configuration.IS_CACHE_ON, lruON); 226 | return HerDB.create(conf, DIRECTORY); 227 | } 228 | 229 | } 230 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/herdb/UnsupportedException.java: -------------------------------------------------------------------------------- 1 | package org.herDB.herdb; 2 | 3 | /** 4 | * 不支持的操作异常 5 | * @author funer 6 | * 7 | */ 8 | @SuppressWarnings("serial") 9 | public class UnsupportedException extends RuntimeException { 10 | 11 | public UnsupportedException(String method, Object obj){ 12 | 13 | super("the method: " + method + "of class:" + obj.getClass() + "isn't supported"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/index/BufferedBlock.java: -------------------------------------------------------------------------------- 1 | package org.herDB.index; 2 | 3 | /** 4 | * 文件缓冲块的大小,用来加快读写文件,类似于BufferByte 5 | * 6 | * @author funer 7 | */ 8 | public class BufferedBlock { 9 | // 缓冲大小 10 | protected final int capacity; 11 | // 缓冲池操作指针指示的位置 12 | protected int position; 13 | // 缓冲的具体二进制内容 14 | protected final byte[] container; 15 | // 缓冲区域有效区的截止边 16 | protected int limit; 17 | 18 | // 用来记录文件块在文件中的具体的偏移 19 | protected long offset; 20 | 21 | protected BufferedBlock(int capacity, int position) { 22 | 23 | this.capacity = capacity; 24 | this.position = position; 25 | this.container = new byte[capacity]; 26 | } 27 | 28 | protected byte[] getBlock() { 29 | 30 | return container; 31 | } 32 | 33 | // 只在此更改offset,将position与offset加 skip 34 | protected void advance(int skip) { 35 | 36 | position += skip; 37 | offset += skip; 38 | } 39 | 40 | protected int getPosition() { 41 | 42 | return this.position; 43 | } 44 | 45 | protected int setLimit(int limit) { 46 | 47 | this.limit = limit; 48 | return this.limit; 49 | } 50 | 51 | // 获取该读取文件块的有效大小 52 | protected int getLimit() { 53 | 54 | return limit; 55 | } 56 | 57 | // block剩余的空间 58 | protected int left() { 59 | 60 | return limit - position; 61 | } 62 | 63 | protected long getOffset() { 64 | 65 | return offset; 66 | } 67 | 68 | // 将文件指针置于块头 69 | protected BufferedBlock placeHeader() { 70 | 71 | position = 0; 72 | return this; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/index/IndexMemoryByte.java: -------------------------------------------------------------------------------- 1 | package org.herDB.index; 2 | 3 | import org.herDB.utils.NumberPacker; 4 | 5 | import java.util.Arrays; 6 | 7 | 8 | /** 9 | * 内存字节数组的类;其字节数组长度为: capacity * 2 * SlotSize + 8 10 | * 11 | * @author funeyu 12 | */ 13 | public final class IndexMemoryByte { 14 | 15 | // 内存索引的全部字节数据 16 | private byte[] bytes; 17 | // 这个值用来与做&hash计算得出index, 为2^n 18 | private int capacity; 19 | // 标记attachedSlots里用到哪一个attachedSlot 20 | private int current; 21 | 22 | private IndexMemoryByte(int capacity, int current) { 23 | 24 | this.capacity = capacity; 25 | this.current = current; 26 | this.bytes = new byte[(capacity << 1) * Slot.slotSize + 8]; 27 | } 28 | 29 | private IndexMemoryByte(int capacity, int current, byte[] bytes) { 30 | 31 | this.capacity = capacity; 32 | this.current = current; 33 | this.bytes = bytes; 34 | } 35 | 36 | /** 37 | * 初始一个空的IndexMemory的容器; 38 | * 39 | * @param capacity 40 | * @param current 41 | * @return 42 | */ 43 | public static IndexMemoryByte init(int capacity, int current) { 44 | 45 | return new IndexMemoryByte(capacity, current); 46 | } 47 | 48 | /** 49 | * 通过bytes 数组生成一个IndexMemoryByte 50 | * 51 | * @param bytes 52 | * @return 53 | */ 54 | public static IndexMemoryByte open(byte[] bytes) { 55 | 56 | int capacity = NumberPacker.unpackInt(new byte[]{ 57 | bytes[0], 58 | bytes[1], 59 | bytes[2], 60 | bytes[3] 61 | }); 62 | 63 | int current = NumberPacker.unpackInt(new byte[]{ 64 | bytes[5], 65 | bytes[6], 66 | bytes[7], 67 | bytes[8] 68 | }); 69 | 70 | return new IndexMemoryByte(capacity, current, bytes); 71 | } 72 | 73 | /** 74 | * 根据index获取一个slot的内存字节数组 75 | * 76 | * @param index 77 | * @return 78 | */ 79 | public byte[] slotBytes(int index) { 80 | 81 | return Arrays.copyOfRange(bytes, index * Slot.slotSize + 8, 82 | (index + 1) * Slot.slotSize + 8); 83 | } 84 | 85 | /** 86 | * 根据hash函数计算出来的hash值,去计算slot的序号 87 | * 88 | * @param hash 89 | * @return 对应的slot的序号 90 | */ 91 | public int slotIndexFor(int hash) { 92 | 93 | return hash & (capacity - 1); 94 | } 95 | 96 | /** 97 | * 根据index获取hashcode 98 | * 99 | * @param index: 为slot的索引值 100 | * @return 101 | */ 102 | public int getHashCode(int index) { 103 | 104 | return Slot.getHashCode(slotBytes(index)); 105 | } 106 | 107 | /** 108 | * 根据index获取索引中,value在文件中偏移 109 | * 110 | * @param index 111 | * @return 112 | */ 113 | public long getFilePosition(int index) { 114 | 115 | return Slot.getFileInfo(slotBytes(index)); 116 | } 117 | 118 | /** 119 | * 根据index获取与之相邻的后继Slot的索引值 120 | * 121 | * @param index 122 | * @return 123 | */ 124 | public int getAttachedSlot(int index) { 125 | 126 | return Slot.getAttachedSlot(slotBytes(index)); 127 | } 128 | 129 | /** 130 | * 设置preIndex的attachedSlot序号为:thisIndex 131 | * 132 | * @param preIndex 133 | * @param thisIndex 134 | */ 135 | public void setAttachedSlot(int preIndex, int thisIndex) { 136 | 137 | byte[] attachedSlotIndex = NumberPacker.packInt(thisIndex); 138 | for (int i = 0; i < 4; i++) { 139 | bytes[preIndex * Slot.slotSize + 8 + 9 + i] = attachedSlotIndex[i]; 140 | } 141 | } 142 | 143 | /** 144 | * 将序号为index的slot的数据信息改成新的哈希,文件指针, 后继slot 145 | * 146 | * @param hc hashCode 147 | * @param fp filePosition 148 | * @param as attachedSlot 149 | * @param index 150 | */ 151 | public void replaceSlot(int hc, long fp, int as, int index) { 152 | 153 | byte[] slotBytes = Slot.generate(hc, fp, as); 154 | for (int i = 0, length = slotBytes.length; i < length; i++) { 155 | bytes[index * Slot.slotSize + 8 + i] = slotBytes[i]; 156 | } 157 | } 158 | 159 | // 返回内存索引的capacity 160 | public int capacity() { 161 | 162 | return capacity; 163 | } 164 | 165 | // 将current自增 166 | public void incCurrent() throws IndexOutofRangeException { 167 | 168 | if (++current > capacity) { 169 | throw new IndexOutofRangeException(); 170 | } 171 | 172 | } 173 | 174 | /** 175 | * 在attachedSlots里获取current 下一个slot, 176 | * 每获取一次current 都要自增一次 177 | *
178 |      * attachedSlots full的时候,throws Exception
179 |      * 
180 | * 181 | * @return 182 | * @throws Exception 183 | */ 184 | public int nextCurrent() throws IndexOutofRangeException { 185 | 186 | int curr = current; 187 | 188 | incCurrent(); 189 | return curr + capacity; 190 | } 191 | 192 | /** 193 | * 在attachedSlots获取current值; 194 | * 因为一般在扩容的新的索引内存数据,不会有IndexOutOfRangeException 195 | * 196 | * @return 197 | */ 198 | public int nextCurrentSafely() { 199 | 200 | int oldIndex = current; 201 | current++; 202 | return oldIndex + capacity; 203 | } 204 | 205 | // 返回该内存索引的所有数据 206 | public byte[] Bytes() { 207 | 208 | return bytes; 209 | } 210 | 211 | // 写入capacity到bytes 212 | public IndexMemoryByte writeCapacity() { 213 | 214 | byte[] capacityBytes = NumberPacker.packInt(capacity); 215 | for (int i = 0; i < 4; i++) { 216 | bytes[i] = capacityBytes[i]; 217 | } 218 | 219 | return this; 220 | } 221 | 222 | // 写入attachedSlots用到哪一个slot 223 | public IndexMemoryByte writeCurrent() { 224 | 225 | byte[] currentBytes = NumberPacker.packInt(current); 226 | for (int i = 0; i < 4; i++) { 227 | bytes[i + 4] = currentBytes[i]; 228 | } 229 | 230 | return this; 231 | } 232 | 233 | // 释放相应的内存 234 | // to do: check it's fine or not to call system.gc() 235 | public void release() { 236 | 237 | bytes = null; 238 | System.gc(); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/index/IndexOutofRangeException.java: -------------------------------------------------------------------------------- 1 | package org.herDB.index; 2 | 3 | /** 4 | * 内存索引的溢出异常, 5 | * 6 | * @author funeyu 7 | */ 8 | @SuppressWarnings("serial") 9 | public class IndexOutofRangeException extends Exception { 10 | 11 | public IndexOutofRangeException() { 12 | super("the IndexMemory is full and can't find any empty slot in attachedSlots"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/index/IndexSegment.java: -------------------------------------------------------------------------------- 1 | package org.herDB.index; 2 | 3 | import com.sun.istack.internal.NotNull; 4 | import org.herDB.herdb.Configuration; 5 | import org.herDB.store.FSDirectory; 6 | import org.herDB.store.InputOutData; 7 | import org.herDB.utils.Bytes; 8 | import org.herDB.utils.Hash; 9 | import org.herDB.utils.NumberPacker; 10 | 11 | import java.io.IOException; 12 | import java.util.Arrays; 13 | import java.util.concurrent.locks.ReentrantLock; 14 | 15 | 16 | 17 | /** 18 | * 分片索引文件的类 19 | * 20 | * @author funeyu 21 | */ 22 | @SuppressWarnings("serial") 23 | public class IndexSegment extends ReentrantLock { 24 | 25 | private IndexMemoryByte indexMemoryByte; 26 | // 为文件的名称,这里索引文件与数据文件文件名称一致 27 | private String fileName; 28 | // todo: 放在configuration里 29 | // 索引文件的后缀 30 | private final static String INDEXSUFFIX = ".index"; 31 | // 数据文件的后缀 32 | private final static String DATASUFFIX = ".data"; 33 | // 临时文件的后缀名 34 | private final static String TEMFILESUFFIX = ".tep"; 35 | // 扩容不能超过的最大容量 36 | private final static int MAXSIZE = 2 << 26; 37 | // main.java.org.herDB.index io操作的入口类 38 | private InputOutData fsIndex; 39 | // data io操作的入口类 40 | private InputOutData fsData; 41 | // FSDirectory的门面 42 | private FSDirectory fsd; 43 | // 读取文件用到的文件块大小 默认先 64KB的大小 44 | private int BufferedSize = 1 << 10; 45 | 46 | /** 47 | * @param campacity 48 | * @param current 49 | * @param fileName 50 | * @param fs 51 | * @param fsData 52 | * @param fsd 53 | * @param isFirst 54 | */ 55 | private IndexSegment(int campacity, int current, String fileName, InputOutData fs, 56 | InputOutData fsData, FSDirectory fsd, boolean isFirst) { 57 | this.indexMemoryByte = isFirst ? IndexMemoryByte.init(campacity, current) : 58 | IndexMemoryByte.open(fsd.readIndexFully(fileName + INDEXSUFFIX)); 59 | this.fileName = fileName; 60 | this.fsIndex = fs; 61 | this.fsData = fsData; 62 | this.fsd = fsd; 63 | } 64 | 65 | /** 66 | * 根据FSDirectory创建或者打开IndexSegment,首次则创建IndexSegment, 67 | * 非首次则打开IndexSegment 68 | * 69 | * @param fsd 70 | * @param fileName 71 | * @return 72 | * @throws Exception 73 | */ 74 | public final static IndexSegment createIndex(FSDirectory fsd, String fileName, Configuration conf) 75 | throws Exception { 76 | 77 | boolean first = false; 78 | 79 | // index文件不存在则新建index文件 80 | if (!fsd.isExsit(fileName + INDEXSUFFIX) && (first = true)) 81 | fsd.touchFile(fileName + INDEXSUFFIX); 82 | InputOutData fsIndex = fsd.createDataStream(fileName + INDEXSUFFIX, false); 83 | 84 | if (!fsd.isExsit(fileName + DATASUFFIX)) 85 | fsd.touchFile(fileName + DATASUFFIX); 86 | InputOutData fsData = fsd.createDataStream(fileName + DATASUFFIX, conf.isOnlyRead()); 87 | 88 | if (first) { 89 | return new IndexSegment(conf.get(Configuration.SLOTS_CAPACITY), 0, fileName, fsIndex, 90 | fsData, fsd, true); 91 | } 92 | 93 | byte[] bytes = fsIndex.readFully(); 94 | return new IndexSegment(NumberPacker.unpackInt(Arrays.copyOfRange(bytes, 0, 4)), 95 | NumberPacker.unpackInt(Arrays.copyOfRange(bytes, 4, 8)), 96 | fileName, fsIndex, fsData, fsd, false); 97 | } 98 | 99 | 100 | /** 101 | * put操作的实现; 102 | *
    103 | *
  • 更新内存索引的数据
  • 104 | *
  • 往磁盘里写入文件
  • 105 | *
106 | * 107 | * @param key 108 | * @param value 109 | * @throws IOException 110 | */ 111 | public void put(byte[] key, byte[] value) throws IOException { 112 | 113 | // key经FVHash1的hash函数得出hash值 114 | int index = Hash.FNVHash1(key) & indexMemoryByte.capacity() - 1; 115 | // key利用系统函数hashCode的出hashcode 116 | int hashcode = Hash.KeyHash(key); 117 | // 标记是否在attachedSlot 118 | boolean isInAttachedSlot = false; 119 | 120 | lock(); 121 | try { 122 | int hc; 123 | 124 | while ((hc = indexMemoryByte.getHashCode(index)) != 0) { 125 | isInAttachedSlot = true; 126 | 127 | // put的key可能与之前已加入的数据相等 128 | if (hashcode == hc) { 129 | // 获取index相应的key磁盘数据 130 | byte[] oldkey = keyData(indexMemoryByte.getFilePosition(index)); 131 | 132 | // 用新的索引数据替换旧的key数据, 133 | if (Arrays.equals(oldkey, key)) { 134 | int attachedslot = indexMemoryByte.getAttachedSlot(index); 135 | indexMemoryByte.replaceSlot(hashcode, fsData.maxOffSet(), attachedslot, index); 136 | 137 | break; 138 | } 139 | } 140 | 141 | if (indexMemoryByte.getAttachedSlot(index) == 0) { 142 | int oldindex = index; 143 | try { 144 | index = indexMemoryByte.nextCurrent(); 145 | } catch (IndexOutofRangeException iore) { 146 | // 如果catch indexoutofrangeException 就开始扩容 147 | try { 148 | resize(); 149 | } catch (Exception e) { 150 | 151 | e.printStackTrace(); 152 | } 153 | // 扩容后重新计算index 154 | index = Hash.FNVHash1(key) & (indexMemoryByte.capacity() - 1); 155 | isInAttachedSlot = false; 156 | 157 | continue; 158 | } 159 | // 设置上个slot 与本slot的关联 160 | indexMemoryByte.setAttachedSlot(oldindex, index); 161 | // 更新slot的数据 162 | indexMemoryByte.replaceSlot(hashcode, fsData.maxOffSet(), 0, index); 163 | break; 164 | } else { 165 | index = indexMemoryByte.getAttachedSlot(index); 166 | } 167 | 168 | } 169 | // 没有发生hash碰撞的情况 170 | if (!isInAttachedSlot) { 171 | indexMemoryByte.replaceSlot(hashcode, fsData.maxOffSet(), 0, index); 172 | } 173 | // 写入key/value的数据到磁盘 174 | fsData.append(Bytes.wrapData(key, value)); 175 | } finally { 176 | unlock(); 177 | } 178 | } 179 | 180 | /** 181 | * 根据 key 获取 value的字节数组; 182 | * 183 | * @param key 184 | * @return byte[] or null 185 | */ 186 | public byte[] get(byte[] key) { 187 | 188 | int index = Hash.FNVHash1(key) & indexMemoryByte.capacity() - 1; 189 | int hashcode = Hash.KeyHash(key); 190 | 191 | lock(); 192 | try { 193 | do { 194 | if (indexMemoryByte.getHashCode(index) == hashcode) { 195 | long filepo = indexMemoryByte.getFilePosition(index); 196 | try { 197 | // key/value磁盘数据格式: 198 | // datalength(4 bytes) + keylength(4 bytes) + key(raw data) + value(raw data) 199 | // datalength的大小:4 + key的字节长度 + value的字节长度 200 | long start = System.currentTimeMillis(); 201 | int datalen = NumberPacker.unpackInt( 202 | fsData.position(filepo) 203 | .readSequentially(4)); 204 | int keylen = NumberPacker.unpackInt( 205 | fsData.readSequentially(4)); 206 | int valuelen = datalen - keylen - 4; 207 | long end = System.currentTimeMillis(); 208 | long during = end - start; 209 | // note: key的hashcode相等的情况下也有可能key不一致 210 | if (Arrays.equals(fsData.readSequentially(keylen), key)) { 211 | byte[] value = fsData.readSequentially(valuelen); 212 | return value; 213 | } 214 | } catch (IOException e) { 215 | 216 | e.printStackTrace(); 217 | return null; 218 | } 219 | } 220 | } while ((index = indexMemoryByte.getAttachedSlot(index)) != 0); 221 | } finally { 222 | unlock(); 223 | } 224 | return null; 225 | } 226 | 227 | /** 228 | * 根据 key字节查找判断是否存在 229 | * 230 | * @return 231 | */ 232 | public boolean contains(byte[] key) { 233 | 234 | return get(key) == null ? false : true; 235 | } 236 | 237 | /** 238 | * 每个阶段的操作最终都要commit,如果在commit阶段,程序在执行扩容的话,就该等待程序执行完成 所以加lock() 239 | */ 240 | public void commit() { 241 | lock(); 242 | try { 243 | close(); 244 | } finally { 245 | unlock(); 246 | } 247 | } 248 | 249 | /** 250 | * 根据offset去获取key的bytes,由于offset指itemData的开始段 251 | *
itemData格式: itemData.length(4字节) + key.length(4字节) + key/value(n字节)
252 | * 253 | * @param offset: itemData的开头 254 | * @return 255 | * @throws IOException 256 | */ 257 | private byte[] keyData(long offset) throws IOException { 258 | 259 | // 获取key数据的长度 260 | int keyLength = NumberPacker.unpackInt(fsData.seek(offset + 4, 4)); 261 | 262 | return fsData.readSequentially(keyLength); 263 | } 264 | 265 | /** 266 | * 索引文件达到full即attachedSlots没有可以再利用 需要扩容 267 | * 扩容的时候:从磁盘文件里顺序读取文件判断是否该数据被删除 268 | * 并写到另一个文件里 269 | * 270 | * @throws Exception 271 | */ 272 | private void resize() throws Exception { 273 | 274 | int newCap = 0; 275 | 276 | if ((newCap = indexMemoryByte.capacity() << 1) > MAXSIZE) { 277 | throw new IllegalArgumentException( 278 | "The comapacity:`" + newCap + "` is reaching its maxsize and can`t expend anymore"); 279 | } 280 | 281 | // 用来读文件的缓存的block 282 | ReadingBufferedBlock readingBlock = ReadingBufferedBlock.allocate(BufferedSize); 283 | // 用来写文件的缓存的block 284 | WritingBufferedBlock writingBlock = WritingBufferedBlock.allocate(BufferedSize); 285 | // 将写文件的缓存block的limit设置为最大 286 | writingBlock.setLimit(BufferedSize); 287 | 288 | // 文件用来写去除无用数据 289 | InputOutData temData = fsd.createDataStream(fileName + TEMFILESUFFIX, false); 290 | IndexMemoryByte tempMemoryByte = IndexMemoryByte.init(newCap, 0); 291 | 292 | // 每次resize之前,先将文件的指针置于开头的位置,便于从头开始顺序读 293 | fsData.jumpHeader(); 294 | while (readingBlock.placeHeader() 295 | .setLimit(fsData.readBlock(readingBlock.getBlock())) != -1) { 296 | byte[] resultData; 297 | while ((resultData = readingBlock.nextItem()) != null) { 298 | byte[] keyBytes = Bytes.extractKey(resultData); 299 | long Offset = Bytes.extractOffset(resultData); 300 | byte[] itemData = Bytes.extractItemData(resultData); 301 | 302 | // 有效的itemData数据,添加到writingBlock 303 | if (isItemValid(keyBytes, Offset)) { 304 | long newOffset = writingBlock.getOffset(); 305 | putOnExtension(keyBytes, newOffset, tempMemoryByte); 306 | 307 | if (writingBlock.hasRoomFor(itemData)) { 308 | writingBlock.wrap(itemData); 309 | } else { 310 | temData.append(writingBlock.flush()); 311 | writingBlock.wrap(itemData); 312 | } 313 | } 314 | } 315 | } 316 | temData.append(writingBlock.flush()); 317 | 318 | // 删除旧的文件 319 | fsData.deleteFile(); 320 | temData.reName(fileName + DATASUFFIX); 321 | fsData = temData; 322 | indexMemoryByte = tempMemoryByte; 323 | } 324 | 325 | /** 326 | * 不加锁的put 只在扩容或者campact的时候调用,添加新的索引信息 327 | * 328 | * @param key 存储的key 字节数组 329 | * @param offset rawdata在磁盘文件的偏移 330 | * @param indexMemory 另一内存索引用来扩充等等 331 | */ 332 | private void putOnExtension(byte[] key, long offset, IndexMemoryByte indexMemory) { 333 | 334 | int index = Hash.FNVHash1(key) & (indexMemory.capacity() - 1); 335 | int hc = Hash.KeyHash(key); 336 | // 标记最终的slot是否在attachedSlots里 337 | boolean isInAttached = false; 338 | 339 | if (indexMemory.getHashCode(index) != 0) { 340 | isInAttached = true; 341 | 342 | while (indexMemory.getAttachedSlot(index) != 0) { 343 | index = indexMemory.getAttachedSlot(index); 344 | } 345 | } 346 | 347 | // 新建slot, 并将slot的index 更新到上一个slot的attachedSlot 348 | if (isInAttached) { 349 | int newIndex = indexMemory.nextCurrentSafely(); 350 | indexMemory.setAttachedSlot(index, newIndex); 351 | indexMemory.replaceSlot(hc, offset, 0, newIndex); 352 | return; 353 | } 354 | indexMemory.replaceSlot(hc, offset, 0, index); 355 | } 356 | 357 | /** 358 | * 从磁盘读取每个item,并在索引内存里判断其是否有效 359 | * 360 | * @return true: 无效的item; false:有效的item 361 | */ 362 | private boolean isItemValid(byte[] key, long offset) { 363 | 364 | int index = Hash.FNVHash1(key) & indexMemoryByte.capacity() - 1; 365 | 366 | do { 367 | if (indexMemoryByte.getFilePosition(index) == offset) { 368 | return true; 369 | } 370 | } while ((index = indexMemoryByte.getAttachedSlot(index)) != 0); 371 | 372 | return false; 373 | } 374 | 375 | /** 376 | * 将内存的index文件flush到磁盘里 377 | */ 378 | private void close() { 379 | // 写入索引的容量值 attachedSlots用到的current值 380 | indexMemoryByte.writeCapacity() 381 | .writeCurrent(); 382 | 383 | try { 384 | fsIndex.deleteFile() 385 | .createNewFile() 386 | .flush(indexMemoryByte.Bytes()); 387 | } catch (IOException e) { 388 | 389 | e.printStackTrace(); 390 | } 391 | indexMemoryByte.release(); 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/index/ReadingBufferedBlock.java: -------------------------------------------------------------------------------- 1 | package org.herDB.index; 2 | 3 | import org.herDB.utils.Bytes; 4 | import org.herDB.utils.NumberPacker; 5 | 6 | import java.util.Arrays; 7 | 8 | 9 | public class ReadingBufferedBlock extends BufferedBlock { 10 | 11 | // item被分割在上一block的数据 12 | private byte[] splitedBytes; 13 | 14 | private ReadingBufferedBlock(int capacity, int position) { 15 | super(capacity, position); 16 | } 17 | 18 | public static ReadingBufferedBlock allocate(int capacity) { 19 | 20 | return new ReadingBufferedBlock(capacity, 0); 21 | } 22 | 23 | /** 24 | * 迭代取出block的item,每个item的格式: 25 | *
 26 |      * datalength | keylength | key | value
27 | * datalength = 4 + key.length + value.length 28 | *
29 | * 30 | * @return 若无则返回 null,有完整item 则返回 offset(5字节) + item的字节数组 31 | */ 32 | public byte[] nextItem() { 33 | 34 | byte[] itemBytes; 35 | //block的开头,要与之前的block的splitedBytes合并 36 | if (position == 0) { 37 | if (splitedBytes != null) { 38 | itemBytes = joinBytes(splitedBytes); 39 | return Bytes.join(NumberPacker.packLong(getOffset() - itemBytes.length), itemBytes); 40 | } 41 | } 42 | 43 | int itemLen; 44 | long offSetData = getOffset(); 45 | // 可以直接读取itemLen 46 | if (left() >= 4) { 47 | itemLen = getInt(); 48 | // 可以直接读取到完整的item数据 49 | if (left() >= itemLen) { 50 | byte[] itemLenBytes = NumberPacker.packInt(itemLen); 51 | byte[] keyValueBytes = getBytes(itemLen); 52 | return Bytes.join(NumberPacker.packLong(offSetData), Bytes.join(itemLenBytes, keyValueBytes)); 53 | } else { 54 | // 不能读取完整的item数据,返回null 55 | splitedBytes = Bytes.join(NumberPacker.packInt(itemLen), leftBytes()); 56 | 57 | return null; 58 | } 59 | } else { 60 | // 不能完整获取itemLen 61 | splitedBytes = leftBytes(); 62 | 63 | return null; 64 | } 65 | } 66 | 67 | // 根据splitedBytes 合并item 68 | private byte[] joinBytes(byte[] splitedBytes) { 69 | 70 | byte[] thisPieces; 71 | if (splitedBytes.length >= 4) { 72 | int itemLen = NumberPacker.unpackInt(new byte[]{ 73 | splitedBytes[0], 74 | splitedBytes[1], 75 | splitedBytes[2], 76 | splitedBytes[3] 77 | }); 78 | thisPieces = getBytes(itemLen - splitedBytes.length + 4); 79 | return Bytes.join(splitedBytes, thisPieces); 80 | } 81 | 82 | // 磁盘itemLen数据被分开在两个block,先组得出itemlen的数值 83 | int itemLen = NumberPacker.unpackInt( 84 | Bytes.join(splitedBytes, getBytes(4 - splitedBytes.length))); 85 | 86 | return Bytes.join(NumberPacker.packInt(itemLen), getBytes(itemLen)); 87 | } 88 | 89 | //获取当前position位置的int数字 90 | private int getInt() { 91 | 92 | int oldPosi = position; 93 | advance(4); 94 | return NumberPacker.unpackInt(new byte[]{ 95 | container[oldPosi], 96 | container[oldPosi + 1], 97 | container[oldPosi + 2], 98 | container[oldPosi + 3] 99 | }); 100 | } 101 | 102 | // 获取该缓冲长度为span的字节数组 103 | private byte[] getBytes(int span) { 104 | 105 | if (span == 0) { 106 | return null; 107 | } 108 | 109 | int oldPo = getPosition(); 110 | advance(span); 111 | return Arrays.copyOfRange(container, oldPo, oldPo + span); 112 | } 113 | 114 | /** 115 | * 返回剩余的字节数组 116 | * 117 | * @return 118 | */ 119 | private byte[] leftBytes() { 120 | 121 | // 说明没有剩余, item没被分在两个block里 122 | if (position == limit) { 123 | return null; 124 | } 125 | 126 | int temPo = position; 127 | advance(limit - position); 128 | return Arrays.copyOfRange(container, temPo, limit); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/index/Slot.java: -------------------------------------------------------------------------------- 1 | package org.herDB.index; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.herDB.utils.NumberPacker; 6 | 7 | /** 8 | * 为哈希的槽节点相关的方法集合,每个slot实际上三步分组成 9 | *
 10 |  * <--hashcode-->|<--fileposition-->|<--attachedslot-->
 11 |  * ----4字节-----------5字节----------------4字节--------
 12 |  * 
13 | *

14 | * 此时的Slot相当于static unit class 15 | * 16 | * @author funeyu 17 | */ 18 | public final class Slot { 19 | // 每个slot 20 | public final static int slotSize = (4 + 5 + 4); 21 | 22 | private Slot() { 23 | } 24 | 25 | ; 26 | 27 | /** 28 | * 由hc fp as三个参数去组装成一个byte[slotSize] 29 | * 30 | * @param hashcode 31 | * @param fileposition 5 byte长度的long数 最大值为 256^5 1T 32 | * @param attachedslot 33 | */ 34 | public static byte[] generate(int hashcode, long fileposition, int attachedslot) { 35 | 36 | byte[] bytes = new byte[slotSize]; 37 | 38 | byte[] hc = NumberPacker.packInt(hashcode); 39 | for (int i = 0; i < 4; i++) 40 | bytes[i] = hc[i]; 41 | 42 | byte[] fp = NumberPacker.packLong(fileposition); 43 | for (int i = 0; i < 5; i++) 44 | bytes[i + 4] = fp[i]; 45 | 46 | byte[] as = NumberPacker.packInt(attachedslot); 47 | for (int i = 0; i < 4; i++) 48 | bytes[i + 9] = as[i]; 49 | 50 | return bytes; 51 | } 52 | 53 | /** 54 | * 设置索引号为slot的槽attachedSlot 值为attachedslot 55 | * 56 | * @param slot 57 | * @param attachedslot 58 | * @param bytes 为索引的全量内存数据 59 | */ 60 | public static void setAttachedSlot(int slot, int attachedslot, byte[] bytes) { 61 | 62 | byte[] attach = NumberPacker.packInt(attachedslot); 63 | for (int i = 0; i < 4; i++) { 64 | bytes[slot * slotSize + 8 + 9 + i] = attach[i]; 65 | } 66 | } 67 | 68 | 69 | /** 70 | * 获取该slot下 相对应key的hashcode 71 | * 72 | * @param bytes 73 | * @return 74 | */ 75 | public static int getHashCode(byte[] bytes) { 76 | 77 | return NumberPacker.unpackInt(Arrays.copyOfRange(bytes, 0, 4)); 78 | } 79 | 80 | /** 81 | * 获取hash索引某slots对应的file文件位置信息 82 | * 83 | * @param bytes 84 | * @return 85 | */ 86 | public static long getFileInfo(byte[] bytes) { 87 | 88 | return NumberPacker.unpackLong(Arrays.copyOfRange(bytes, 4, 9)); 89 | } 90 | 91 | /** 92 | * 获取该slot的后继slot的id 93 | * 94 | * @param bytes 95 | * @return 96 | */ 97 | public static int getAttachedSlot(byte[] bytes) { 98 | 99 | return NumberPacker.unpackInt(Arrays.copyOfRange(bytes, 9, slotSize)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/index/WritingBufferedBlock.java: -------------------------------------------------------------------------------- 1 | package org.herDB.index; 2 | 3 | import java.util.Arrays; 4 | 5 | public class WritingBufferedBlock extends BufferedBlock { 6 | 7 | private WritingBufferedBlock(int capacity, int position) { 8 | super(capacity, position); 9 | } 10 | 11 | public static WritingBufferedBlock allocate(int capacity) { 12 | 13 | return new WritingBufferedBlock(capacity, 0); 14 | } 15 | 16 | /** 17 | * 判断写block是否有空间 容纳itemData 18 | * 19 | * @param itemData key/value的数据 20 | * @return true 有空间, false 没空间 21 | */ 22 | public boolean hasRoomFor(byte[] itemData) { 23 | 24 | if (left() >= itemData.length) { 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | /** 31 | * 将文件缓冲读出,并将该文件缓冲置头 32 | * 33 | * @return 34 | */ 35 | public byte[] flush() { 36 | 37 | int oldPo = position; 38 | placeHeader(); 39 | return Arrays.copyOfRange(container, 0, oldPo); 40 | } 41 | 42 | /** 43 | * 将data的字节数组包装到该缓冲文件中 44 | * 45 | * @param data 46 | * @return 47 | */ 48 | public BufferedBlock wrap(byte[] data) { 49 | 50 | if (data == null) { 51 | return this; 52 | } 53 | 54 | for (int i = 0, length = data.length; i < length; i++) { 55 | container[i + position] = data[i]; 56 | } 57 | 58 | advance(data.length); 59 | return this; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/net/Client.java: -------------------------------------------------------------------------------- 1 | package org.herDB.net; 2 | 3 | import org.herDB.utils.Bytes; 4 | import org.herDB.utils.NumberPacker; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.net.Socket; 10 | import java.util.UUID; 11 | 12 | /** 13 | * Created by funeyu on 17/1/1. 14 | */ 15 | public class Client { 16 | 17 | // 生成一个uuid标识该client;每次都将该id传送给server,用来校验等操作 18 | private String uuid ; 19 | private Socket socket; 20 | private InputStream in; 21 | private OutputStream os; 22 | 23 | private Client(Socket socket, String uuid, InputStream in, OutputStream os) { 24 | this.socket = socket; 25 | this.uuid = uuid; 26 | this.in = in; 27 | this.os = os; 28 | } 29 | 30 | public static Client initClient(int port, String host) throws IOException{ 31 | Socket socket = new Socket(host, port); 32 | String uuid = UUID.randomUUID().toString(); 33 | InputStream in = socket.getInputStream(); 34 | OutputStream os = socket.getOutputStream(); 35 | Client client = new Client(socket, uuid, in, os); 36 | return client; 37 | } 38 | 39 | public String get(String key) throws IOException{ 40 | sendCommand(("get,"+ key).getBytes()); 41 | return read(); 42 | } 43 | 44 | public void put(String key, String value) { 45 | sendCommand(("put,"+ key + "," + value).getBytes()); 46 | } 47 | 48 | public void auth(String token) throws IOException{ 49 | sendCommand(("auth," + token).getBytes()); 50 | String re = read(); 51 | System.out.println(re); 52 | } 53 | 54 | public void close() { 55 | try { 56 | socket.close(); 57 | } catch (IOException e) { 58 | e.printStackTrace(); 59 | } 60 | } 61 | 62 | private void sendCommand(byte[] commandInfo) { 63 | 64 | byte[] callInfo = Bytes.join(uuid.getBytes(), Bytes.join(NumberPacker.packInt(commandInfo.length), commandInfo)); 65 | 66 | try { 67 | os.write(callInfo); 68 | os.flush(); 69 | } catch (IOException e) { 70 | e.printStackTrace(); 71 | } 72 | } 73 | 74 | private String read() throws IOException{ 75 | try { 76 | byte[] lenBytes = new byte[4]; 77 | in.read(lenBytes); 78 | int bytesLen = NumberPacker.unpackInt(lenBytes); 79 | byte[] rawContent = new byte[bytesLen]; 80 | in.read(rawContent); 81 | String result = new String(rawContent); 82 | return result; 83 | 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | 87 | return "error: " + e.toString(); 88 | } 89 | } 90 | 91 | public static void main(String[]args) { 92 | System.out.println(UUID.randomUUID().toString()); 93 | try { 94 | Client client = Client.initClient(8888, "127.0.0.1"); 95 | client.auth("funer"); 96 | Long start = System.currentTimeMillis(); 97 | for(int i = 10000; i < 100000; i++) { 98 | client.put("fuheyu"+ i, "java c" + i); 99 | } 100 | Long end = System.currentTimeMillis(); 101 | System.out.println((end-start) / 1000); 102 | // client.put("fuheyu", "java c art"); 103 | System.out.println(client.get("fuheyu0")); 104 | 105 | client.close(); 106 | // client.put("funer", "java, eclipse"); 107 | // System.out.println(client.get("funer")); 108 | } catch (IOException e) { 109 | e.printStackTrace(); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/net/Commands.java: -------------------------------------------------------------------------------- 1 | package org.herDB.net; 2 | 3 | 4 | import org.herDB.herdb.HerDB; 5 | 6 | /** 7 | * Created by funeyu on 17/1/1. 8 | */ 9 | public class Commands { 10 | 11 | public static String get(String key, HerDB dbStore) { 12 | return dbStore.get(key); 13 | } 14 | 15 | public static void put(String key, String value, HerDB dbStore) { 16 | dbStore.put(key, value); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/net/Server.java: -------------------------------------------------------------------------------- 1 | package org.herDB.net; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.nio.ByteBuffer; 6 | import java.nio.ByteOrder; 7 | import java.nio.channels.SelectionKey; 8 | import java.nio.channels.Selector; 9 | import java.nio.channels.ServerSocketChannel; 10 | import java.nio.channels.SocketChannel; 11 | import java.util.Iterator; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | import java.util.concurrent.atomic.AtomicBoolean; 14 | 15 | import org.herDB.herdb.HerDB; 16 | import org.herDB.utils.Bytes; 17 | import org.herDB.utils.NumberPacker; 18 | 19 | /** 20 | * Created by funeyu on 16/12/31. 21 | */ 22 | public class Server { 23 | 24 | private int port; 25 | private static Server singleServer; 26 | private AtomicBoolean listenning = new AtomicBoolean(true); 27 | // 记录clients的认证信息 28 | private static ConcurrentHashMap authedClients = new ConcurrentHashMap(); 29 | 30 | private static HerDB dbStore; 31 | 32 | public static Server initServer(int port) { 33 | try { 34 | dbStore = HerDB.bootStrap(true, 8192); 35 | } catch (Exception e) { 36 | System.out.println("初始Server出错了"); 37 | e.printStackTrace(); 38 | } 39 | 40 | if(singleServer == null) { 41 | singleServer = new Server(); 42 | } 43 | 44 | singleServer.port = port; 45 | return singleServer; 46 | } 47 | 48 | public void start() { 49 | new Listenner().start(); 50 | } 51 | 52 | private class Listenner extends Thread { 53 | private ServerSocketChannel acceptChannel = null; 54 | private Selector selector = null; 55 | 56 | public void run() { 57 | try { 58 | acceptChannel = ServerSocketChannel.open(); 59 | acceptChannel.configureBlocking(false); 60 | selector = Selector.open(); 61 | acceptChannel.socket().bind(new InetSocketAddress(port)); 62 | acceptChannel.register(selector, SelectionKey.OP_ACCEPT); 63 | 64 | while(listenning.get()) { 65 | int n = selector.select(); 66 | if(n == 0) { //没有指定的I/O事件发生 67 | continue; 68 | } 69 | Iterator selectionKeys = selector.selectedKeys().iterator(); 70 | while(selectionKeys.hasNext()) { 71 | SelectionKey key = selectionKeys.next(); 72 | selectionKeys.remove(); 73 | 74 | if(key.isAcceptable()) { 75 | ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); 76 | SocketChannel sc = ssc.accept(); 77 | sc.configureBlocking(false); 78 | sc.register(selector, SelectionKey.OP_READ); 79 | } 80 | 81 | if(key.isReadable() && key.isValid()) { 82 | doRead(key); 83 | } 84 | 85 | } 86 | } 87 | } catch (IOException e) { 88 | e.printStackTrace(); 89 | } 90 | } 91 | 92 | /** 93 | * 读取Client 端的command信息 94 | * @param key 95 | * @throws IOException 96 | */ 97 | private void doRead(SelectionKey key) throws IOException { 98 | SocketChannel sc = (SocketChannel) key.channel(); 99 | 100 | try { 101 | ByteBuffer uuidBuffer = ByteBuffer.allocate(36); 102 | int readed = sc.read(uuidBuffer); 103 | if(readed == -1) { // 客户端关闭socket 104 | sc.socket().close(); 105 | return ; 106 | } 107 | 108 | if(readed != 36) { 109 | return ; 110 | } 111 | 112 | String uuid = new String(uuidBuffer.array()); 113 | 114 | ByteBuffer lenBuffer = ByteBuffer.allocate(4); 115 | sc.read(lenBuffer); 116 | lenBuffer.flip(); 117 | lenBuffer.order(ByteOrder.BIG_ENDIAN); 118 | int dataLen = lenBuffer.getInt(); 119 | 120 | if(dataLen > 64 || dataLen < 0) { 121 | sc.write(wrapOutData("comand payload too large")); 122 | return ; 123 | } 124 | ByteBuffer content = ByteBuffer.allocate(dataLen); 125 | sc.read(content); 126 | 127 | handleCommand(key, content.array(), uuid); 128 | } catch (Exception e) { 129 | key.cancel(); 130 | sc.socket().close(); 131 | e.printStackTrace(); 132 | } 133 | } 134 | 135 | /** 136 | * 处理Client端的command请求,如果已经验证过了,就进行,否则直接关闭socket 137 | * commands 信息格式为: 138 | * 'commandType(get or set or auth),commandParam' 139 | * @param key 140 | * @param commands 141 | * @param uuid 142 | */ 143 | private void handleCommand(SelectionKey key, byte[] commands, String uuid) throws IOException{ 144 | SocketChannel sc = (SocketChannel)key.channel(); 145 | String[] commandsInfo = new String(commands).split(","); 146 | // 命令行数据格式不对 147 | if(commandsInfo.length == 0 || commandsInfo.length > 3) { 148 | sc.write(wrapOutData("wrong command info")); 149 | sc.socket().close(); 150 | return ; 151 | } 152 | 153 | if (authedClients.get(uuid) == null) { 154 | if (commandsInfo[0].equals("auth")) { 155 | if(commandsInfo[1].equals("funer")) { 156 | authedClients.put(uuid, Boolean.TRUE); 157 | sc.write(wrapOutData("success: in auth")); 158 | key.interestOps(SelectionKey.OP_READ); 159 | } 160 | } 161 | else { 162 | sc.write(wrapOutData("error: in auth")); 163 | key.interestOps(SelectionKey.OP_READ); 164 | sc.socket().close(); 165 | } 166 | 167 | return ; 168 | } 169 | 170 | // get请求处理 171 | if(commandsInfo[0].equals("get")) { 172 | String result = Commands.get(commandsInfo[1], dbStore); 173 | if(result == null) { 174 | sc.write(wrapOutData("null")); 175 | key.interestOps(SelectionKey.OP_READ); 176 | return ; 177 | } 178 | sc.write(wrapOutData(result)); 179 | key.interestOps(SelectionKey.OP_READ); 180 | } 181 | // put请求的处理 182 | if(commandsInfo[0].equals("put")) { 183 | Commands.put(commandsInfo[1], commandsInfo[2], dbStore); 184 | key.interestOps(SelectionKey.OP_READ); 185 | } 186 | } 187 | 188 | // 坑啊, 这里讲数据返回给client的时候加上size 189 | private ByteBuffer wrapOutData(String data) { 190 | byte[] rawBytes = data.getBytes(); 191 | // 记录数据的长度 192 | byte[] dataLen = NumberPacker.packInt(rawBytes.length); 193 | byte[] wrapedBytes = Bytes.join(dataLen, rawBytes); 194 | ByteBuffer buffer = ByteBuffer.wrap(wrapedBytes); 195 | return buffer; 196 | } 197 | } 198 | 199 | public static void main(String[]args) { 200 | Server server = Server.initServer(8888); 201 | server.start(); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/serializer/PrimitiveType.java: -------------------------------------------------------------------------------- 1 | package org.herDB.serializer; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.HashMap; 5 | 6 | /** 7 | * Created by funeyu on 16/7/23. 8 | */ 9 | enum PrimitiveType { 10 | // 11 | // 这样写是不对的 12 | // INT { 13 | // @Override int weight() { 14 | // return 4; 15 | // } 16 | // @Override Class classify() { 17 | // return Integer.class; 18 | // } 19 | // @Override byte[] serialize(Integer value) { 20 | // ByteBuffer bytes = ByteBuffer.allocate(1 + weight()); 21 | // bytes.put((byte)id()); 22 | // bytes.putInt(((Integer)value).intValue()); 23 | // return bytes.array(); 24 | // } 25 | // @Override Integer deserialize(byte[] bytes, int off, int len) { 26 | // ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, off, len); 27 | // int result = byteBuffer.getInt(); 28 | // return new Integer(result); 29 | // } 30 | // @Override int id() { 31 | // return 1; 32 | // } 33 | // 34 | // }, 35 | INT { 36 | @Override 37 | int weight() { 38 | return 4; 39 | } 40 | 41 | @Override 42 | Class classify() { 43 | return Integer.class; 44 | } 45 | 46 | @Override 47 | byte[] serialize(T value) { 48 | 49 | ByteBuffer bytes = ByteBuffer.allocate(1 + weight()); 50 | bytes.put((byte) id()); 51 | bytes.putInt(((Integer) value).intValue()); 52 | return bytes.array(); 53 | } 54 | 55 | @Override 56 | Integer deserialize(byte[] bytes, int off, int len) { 57 | 58 | ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, off, len); 59 | int result = byteBuffer.getInt(); 60 | return new Integer(result); 61 | } 62 | 63 | @Override 64 | int id() { 65 | return 1; 66 | } 67 | 68 | }, 69 | LONG { 70 | @Override 71 | int weight() { 72 | return 8; 73 | } 74 | 75 | @Override 76 | Class classify() { 77 | return Long.class; 78 | } 79 | 80 | @Override 81 | byte[] serialize(T obj) { 82 | ByteBuffer bytes = ByteBuffer.allocate(1 + weight()); 83 | bytes.put((byte) id()); 84 | bytes.putLong(((Long) obj).longValue()); 85 | return bytes.array(); 86 | } 87 | 88 | @Override 89 | int id() { 90 | return 2; 91 | } 92 | 93 | @Override 94 | Long deserialize(byte[] bytes, int off, int len) { 95 | ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, off, len); 96 | long result = byteBuffer.getLong(); 97 | return new Long(result); 98 | } 99 | }, 100 | SHORT { 101 | @Override 102 | int weight() { 103 | return 2; 104 | } 105 | 106 | @Override 107 | Class classify() { 108 | return Short.class; 109 | } 110 | 111 | @Override 112 | byte[] serialize(T obj) { 113 | ByteBuffer bytes = ByteBuffer.allocate(1 + weight()); 114 | bytes.put((byte) id()); 115 | bytes.putShort(((Short) obj).shortValue()); 116 | return bytes.array(); 117 | } 118 | 119 | @Override 120 | int id() { 121 | return 3; 122 | } 123 | 124 | @Override 125 | Short deserialize(byte[] bytes, int off, int len) { 126 | ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, off, len); 127 | short result = byteBuffer.getShort(); 128 | return new Short(result); 129 | } 130 | }, 131 | BOOLEAN { 132 | @Override 133 | int weight() { 134 | return 1; 135 | } 136 | 137 | @Override 138 | Class classify() { 139 | return Boolean.class; 140 | } 141 | 142 | @Override 143 | byte[] serialize(T obj) { 144 | ByteBuffer bytes = ByteBuffer.allocate(1 + weight()); 145 | bytes.put((byte) id()); 146 | bytes.put(((Boolean) obj).booleanValue() ? (byte) 1 : (byte) 0); 147 | return bytes.array(); 148 | } 149 | 150 | @Override 151 | int id() { 152 | return 4; 153 | } 154 | 155 | @Override 156 | Boolean deserialize(byte[] bytes, int off, int len) { 157 | ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, off, len); 158 | boolean result = byteBuffer.get() == (byte) 1 ? true : false; 159 | return new Boolean(result); 160 | } 161 | }, 162 | FLOAT { 163 | @Override 164 | int weight() { 165 | return 4; 166 | } 167 | 168 | @Override 169 | Class classify() { 170 | return Float.class; 171 | } 172 | 173 | @Override 174 | byte[] serialize(T obj) { 175 | ByteBuffer bytes = ByteBuffer.allocate(1 + weight()); 176 | bytes.put((byte) id()); 177 | bytes.putFloat(((Float) obj).floatValue()); 178 | return bytes.array(); 179 | } 180 | 181 | @Override 182 | int id() { 183 | return 5; 184 | } 185 | 186 | @Override 187 | Float deserialize(byte[] bytes, int off, int len) { 188 | ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, off, len); 189 | float result = byteBuffer.getFloat(); 190 | return new Float(result); 191 | } 192 | }, 193 | INTARRAY { 194 | @Override 195 | int weight() { 196 | return 4; 197 | } 198 | 199 | @Override 200 | Class classify() { 201 | return (new int[]{1}).getClass(); 202 | } 203 | 204 | @Override 205 | byte[] serialize(T obj) { 206 | int[] intArray = (int[]) obj; 207 | ByteBuffer bytes = ByteBuffer.allocate(1 + intArray.length * weight()); 208 | bytes.put((byte) id()); 209 | 210 | for (int i = 0, length = intArray.length; i < length; i++) { 211 | bytes.putInt(intArray[i]); 212 | } 213 | return bytes.array(); 214 | } 215 | 216 | @Override 217 | int id() { 218 | return 101; 219 | } 220 | 221 | @Override 222 | int[] deserialize(byte[] bytes, int off, int len) { 223 | ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, off, len); 224 | int[] results = new int[len >> 2]; 225 | for (int i = 0, length = results.length; i < length; i++) { 226 | results[i] = byteBuffer.getInt(); 227 | } 228 | return results; 229 | } 230 | }; 231 | 232 | // 根据Class类型去获取相应的PrimitiveType 233 | public final static HashMap EnumsClassMap = new HashMap(); 234 | 235 | public final static HashMap EnumIdMap = new HashMap(); 236 | 237 | static { 238 | for (PrimitiveType type : PrimitiveType.values()) { 239 | EnumsClassMap.put(type.classify(), type); 240 | EnumIdMap.put(new Integer(type.id()), type); 241 | } 242 | } 243 | 244 | /** 245 | * 该类型数据占据的byte数 246 | * 247 | * @return 248 | */ 249 | abstract int weight(); 250 | 251 | /** 252 | * 返回Enum所在的Class类型 253 | * 254 | * @return 255 | */ 256 | abstract Class classify(); 257 | 258 | /** 259 | * 将对象序列化成byte类型的数据 260 | * 261 | * @param obj 待序列化的对象 262 | * @param 泛型 T 263 | * @return obj => bytes 264 | */ 265 | abstract byte[] serialize(T obj); 266 | 267 | /** 268 | * 每个枚举数据都返回独自的id; 269 | * 用来存储在磁盘文件中,标记Class类型 270 | * 271 | * @return 272 | */ 273 | abstract int id(); 274 | 275 | /** 276 | * 从bytes中解码成相应的T对象数据 277 | * 278 | * @param bytes 279 | * @param off 280 | * @param len 281 | * @param 282 | * @return 283 | */ 284 | abstract T deserialize(byte[] bytes, int off, int len); 285 | } 286 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/serializer/Reflection.java: -------------------------------------------------------------------------------- 1 | package org.herDB.serializer; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Modifier; 5 | 6 | /** 7 | * Created by funeyu on 16/7/24. 8 | */ 9 | public class Reflection { 10 | 11 | public static Field[] getAllFields(T obj) { 12 | final Field[] fields = obj.getClass().getDeclaredFields(); 13 | return fields; 14 | } 15 | 16 | public static Field[] getAllFields(Class clazz) { 17 | final Field[] fields = clazz.getDeclaredFields(); 18 | return fields; 19 | } 20 | 21 | public static int calculateSize(Field[] fields, Object obj) { 22 | int size = 0; 23 | for (Field field : fields) { 24 | if(!field.getType().isPrimitive()) { 25 | if(field.getType().equals(String.class)) { 26 | try { 27 | arrageField(field, obj); 28 | String str = (String) field.get(obj); 29 | if (str == null) 30 | size += 4; 31 | else 32 | size += 4 + str.length() * 2; 33 | } catch (NoSuchFieldException e) { 34 | e.printStackTrace(); 35 | } catch (IllegalAccessException e) { 36 | e.printStackTrace(); 37 | } 38 | } 39 | } 40 | } 41 | return 1; 42 | } 43 | 44 | public static void arrageField(Field field, T obj) throws NoSuchFieldException, IllegalAccessException { 45 | field.setAccessible(true); 46 | Field modifiersField = Field.class.getDeclaredField("modifiers"); 47 | modifiersField.setAccessible(true); 48 | modifiersField.setInt(field, field.getModifiers() & ~ Modifier.FINAL); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/serializer/Serializer.java: -------------------------------------------------------------------------------- 1 | package org.herDB.serializer; 2 | 3 | /** 4 | * Created by funeyu on 16/7/23. 5 | */ 6 | interface Serializer { 7 | 8 | byte[] serialize(T obj); 9 | 10 | T deserialize(byte[] data); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/serializer/SerializerImp.java: -------------------------------------------------------------------------------- 1 | package org.herDB.serializer; 2 | 3 | /** 4 | * Created by funeyu on 16/7/24. 5 | */ 6 | public final class SerializerImp implements Serializer { 7 | 8 | private final StringSerializer stringSerializer = new StringSerializer(); 9 | 10 | private SerializerImp() { 11 | 12 | } 13 | 14 | public static SerializerImp build() { 15 | return new SerializerImp(); 16 | } 17 | 18 | @Override 19 | public byte[] serialize(T obj) { 20 | if (obj == null) { 21 | return new byte[0]; 22 | } 23 | if (obj instanceof String) { 24 | return stringSerializer.serialize(obj); 25 | } 26 | // 判断是否为基本类型的数据,note: Integer.class.isPrimitive()返回为false; 27 | if (!SerializerUtils.isPrimitive(obj.getClass())) { 28 | throw new IllegalArgumentException("only String and primitive value are supported!"); 29 | } 30 | 31 | return PrimitiveType.EnumsClassMap.get(obj.getClass()) 32 | .serialize(obj); 33 | } 34 | 35 | @Override 36 | public T deserialize(byte[] data) { 37 | byte id = data[0]; 38 | if (id == (byte) stringSerializer.id) { 39 | // 为String类型的数据 40 | return (T) stringSerializer.deserialize(data); 41 | } 42 | T result = PrimitiveType.EnumIdMap.get(new Integer(id)) 43 | .deserialize(data, 1, data.length - 1); 44 | return result; 45 | } 46 | 47 | 48 | private static class StringSerializer implements Serializer { 49 | 50 | // 存储在磁盘中,为string的标记 51 | private int id = 255; 52 | 53 | private StringSerializer() { 54 | } 55 | 56 | @Override 57 | public byte[] serialize(String obj) { 58 | byte[] objBytes = toBytes((java.lang.String) obj); 59 | byte[] resultBytes = new byte[objBytes.length + 1]; 60 | System.arraycopy(new byte[]{(byte) id}, 0, resultBytes, 0, 1); 61 | System.arraycopy(objBytes, 0, resultBytes, 1, objBytes.length); 62 | return resultBytes; 63 | } 64 | 65 | @Override 66 | public String deserialize(byte[] data) { 67 | byte[] dataBytes = new byte[data.length - 1]; 68 | System.arraycopy(data, 1, dataBytes, 0, dataBytes.length); 69 | return new String(dataBytes); 70 | } 71 | 72 | private byte[] toBytes(String str) { 73 | 74 | return str.getBytes(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/serializer/SerializerUtils.java: -------------------------------------------------------------------------------- 1 | package org.herDB.serializer; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * Created by funeyu on 16/7/26. 7 | */ 8 | class SerializerUtils { 9 | 10 | private final static HashMap primitivesMap = new HashMap(); 11 | 12 | static { 13 | primitivesMap.put(Integer.class, Boolean.TRUE); 14 | primitivesMap.put(Float.class, Boolean.TRUE); 15 | primitivesMap.put(Long.class, Boolean.TRUE); 16 | primitivesMap.put(Short.class, Boolean.TRUE); 17 | primitivesMap.put(Double.class, Boolean.TRUE); 18 | primitivesMap.put(Boolean.class, Boolean.TRUE); 19 | primitivesMap.put(Byte.class, Boolean.TRUE); 20 | primitivesMap.put((new int[]{1}).getClass(), Boolean.TRUE); 21 | } 22 | 23 | public static boolean isPrimitive(Class clazz) { 24 | if (clazz.isPrimitive()) { 25 | return true; 26 | } 27 | if (primitivesMap.containsKey(clazz)) { 28 | return true; 29 | } 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/store/FSDirectory.java: -------------------------------------------------------------------------------- 1 | package org.herDB.store; 2 | 3 | import java.io.EOFException; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | 8 | /** 9 | * @author funeyu 10 | */ 11 | public class FSDirectory { 12 | private File directory; 13 | private Lock lock; 14 | private final static String LOCKFILENAME = "herDB.lock"; 15 | 16 | /** 17 | * @param directoryPath:为目录的文件夹文件 18 | */ 19 | private FSDirectory(File directory) { 20 | this.directory = directory; 21 | } 22 | 23 | /** 24 | * 利用创建herDB.lock空文件的锁定策略 25 | * 26 | * @return 如果上锁失败返回null, 成功就返回该FSDirectory实例 27 | */ 28 | private FSDirectory lockDir() { 29 | 30 | if (lock == null) { 31 | lock = new Lock() { 32 | protected boolean acquire() { 33 | File lockFile = new File(directory.getPath() + File.separator + LOCKFILENAME); 34 | try { 35 | if (lockFile.createNewFile()) 36 | return true; 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | } 40 | return false; 41 | } 42 | 43 | public void release() { 44 | File f = new File(directory.getPath(), LOCKFILENAME); 45 | if (f.exists()) 46 | f.delete(); 47 | } 48 | 49 | }; 50 | } 51 | // return lock.lock(10) ? this : null; 52 | return this; 53 | } 54 | 55 | // 将该目录下的.lock文件删除, 解锁文件夹 56 | public void releaseDir() { 57 | lock.release(); 58 | } 59 | 60 | public boolean isExsit(String pathName) { 61 | 62 | File f = new File(directory.getPath(), pathName); 63 | return f.exists() ? true : false; 64 | } 65 | 66 | /** 67 | * 文件不存在的时候,创建一个文件名为fileName的文件 68 | * 69 | * @param pathName 70 | * @return 71 | */ 72 | public File touchFile(String fileName) { 73 | 74 | File newedFile = new File(directory.getPath() + File.separator + fileName); 75 | try { 76 | if (newedFile.createNewFile()) 77 | return newedFile; 78 | } catch (IOException e) { 79 | e.printStackTrace(); 80 | } 81 | return null; 82 | } 83 | 84 | /** 85 | * 将磁盘文件全部读到内存, 方法只用在索引文件 86 | * 87 | * @param name 文件名 88 | * @return 磁盘文件的字节数组 89 | */ 90 | public byte[] readIndexFully(String name) { 91 | try { 92 | RandomAccessFile f = new RandomAccessFile(new File(directory, name), "r"); 93 | int length = (int) f.length(); 94 | byte[] data = new byte[length]; 95 | f.readFully(data); 96 | f.close(); 97 | 98 | return data; 99 | } catch (IOException e) { 100 | 101 | e.printStackTrace(); 102 | } 103 | return null; 104 | } 105 | 106 | /** 107 | * 创建FSDirectory的 最后要将该目录锁定,不让有其他的进程操作数据库; 108 | * 109 | * @param directoryPath 指定的目录路径 110 | * @param delete 目录存在删除为true,不删除为false 111 | * @return 创建成功就返回FSDirectory实例 112 | * @throws IOException 113 | */ 114 | public static FSDirectory create(String directoryPath, boolean delete) throws IOException { 115 | 116 | File file = new File(directoryPath); 117 | if (!file.exists()) { 118 | if (!file.mkdirs()) 119 | throw new IOException("不能创建初始的文件夹:" + directoryPath); 120 | } else { 121 | // 这里有两个出错异常点: 122 | // 1,文件夹已经存在但是 delete为false的情况下,自定义的异常 123 | // 2,删除文件夹里所有的文件出现的系统IOException异常 124 | if (delete ? !deleteFiles(file) : true) 125 | throw new IOException("文件夹存在但是又不能删除或者删除出错:" + directoryPath); 126 | } 127 | return new FSDirectory(file).lockDir(); 128 | } 129 | 130 | /** 131 | * 打开一个目录, 此目录必须是herDB的目录,如果有锁文件就throw exception 然后再进行枷锁的操作 132 | * 133 | * @param directoryPath 要打开的文件夹 134 | * @param onlyRead true:只读不写 false:读写 135 | * @return 136 | */ 137 | public static FSDirectory open(String directoryPath) { 138 | 139 | File file = new File(directoryPath); 140 | if (!file.exists()) 141 | return null; 142 | return new FSDirectory(file).lockDir(); 143 | } 144 | 145 | /** 146 | * 目录下只有文件,没有二级的文件夹,删除目录里所有的文件 留着一个空的目录 147 | * 148 | * @param dir 将要删除文件的目录 149 | * @return 只要有一个删除不成功就返回false 全部删除完成返回true 150 | */ 151 | public static boolean deleteFiles(File dir) { 152 | 153 | for (File f : dir.listFiles()) { 154 | if (!f.delete()) 155 | return false; 156 | } 157 | return true; 158 | } 159 | 160 | /** 161 | * 通过FSDirectory创建FSDataStream,负责目录下所有的数据读写操作 包括index文件的读取,与rawData数据文件的读取 162 | * 163 | * @param name 该目录下的文件名 164 | * @return 165 | * @throws Exception 166 | * @onlyRead 只读:true, 读写:false 167 | * 在打开herdb数据库,只读情况下可以将数据文件映射到堆内存,加快读操作 168 | */ 169 | public InputOutData createDataStream(String name, boolean onlyRead) throws Exception { 170 | 171 | return onlyRead ? new MMapInputStream(this.directory, name) 172 | : new FSDataStream(this.directory, name); 173 | } 174 | 175 | class FSDataStream extends InputOutData { 176 | 177 | private long length; 178 | private long maxOffSet; 179 | private String fileName; 180 | private File file; 181 | private File directory; 182 | private RandomAccessFile raf; 183 | 184 | public FSDataStream(File dir, String name) throws Exception { 185 | 186 | File f = new File(dir, name); 187 | file = f; 188 | directory = dir; 189 | fileName = name; 190 | raf = new RandomAccessFile(f, "rw"); 191 | maxOffSet = length = raf.length(); 192 | 193 | } 194 | 195 | public byte[] readFully() throws IOException { 196 | 197 | byte[] data = new byte[(int) length]; 198 | raf.read(data); 199 | return data; 200 | } 201 | 202 | public void flush(byte[] datas) { 203 | 204 | try { 205 | raf.write(datas, 0, datas.length); 206 | } catch (IOException e) { 207 | e.printStackTrace(); 208 | } 209 | } 210 | 211 | @Override 212 | public byte[] seek(long offset, int size) throws IOException { 213 | 214 | raf.seek(offset); 215 | 216 | byte[] res = new byte[size]; 217 | if (raf.read(res) == -1) 218 | throw new EOFException(); 219 | 220 | return res; 221 | } 222 | 223 | @Override 224 | public FSDataStream append(byte[] data) throws IOException { 225 | 226 | long fileLength = raf.length(); 227 | raf.seek(fileLength); 228 | raf.write(data); 229 | maxOffSet += data.length; 230 | return this; 231 | } 232 | 233 | @Override 234 | public byte[] readSequentially(int size) throws IOException { 235 | 236 | byte[] bytes = new byte[size]; 237 | raf.read(bytes); 238 | return bytes; 239 | } 240 | 241 | @Override 242 | public long maxOffSet() { 243 | 244 | return maxOffSet; 245 | } 246 | 247 | @Override 248 | public InputOutData position(long offset) throws IOException { 249 | this.raf.seek(offset); 250 | return this; 251 | } 252 | 253 | @Override 254 | public InputOutData deleteFile() { 255 | this.file.delete(); 256 | return this; 257 | } 258 | 259 | @Override 260 | public InputOutData createNewFile() throws IOException { 261 | 262 | File f = new File(directory, fileName); 263 | file = f; 264 | raf = new RandomAccessFile(f, "rw"); 265 | length = raf.length(); 266 | return this; 267 | } 268 | 269 | 270 | @Override 271 | public int readBlock(byte[] block) throws IOException { 272 | 273 | return this.raf.read(block); 274 | } 275 | 276 | @Override 277 | public boolean reName(String newName) { 278 | 279 | File newFile = new File(directory, newName); 280 | boolean isok = file.renameTo(newFile); 281 | file = newFile; 282 | 283 | try { 284 | raf = new RandomAccessFile(file, "rw"); 285 | length = raf.length(); 286 | } catch (IOException e) { 287 | isok = false; 288 | e.printStackTrace(); 289 | } 290 | 291 | return isok; 292 | } 293 | 294 | @Override 295 | public void jumpHeader() throws IOException { 296 | 297 | this.raf.seek(0); 298 | } 299 | 300 | 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/store/InputOutData.java: -------------------------------------------------------------------------------- 1 | package org.herDB.store; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * 文件读写涉及到的类 7 | * 8 | * @author fuheu 9 | */ 10 | 11 | /** 12 | * @author funeyu 13 | * 14 | */ 15 | public abstract class InputOutData { 16 | 17 | /** 18 | * 一次将文件所有的内容读入内存中 19 | * 20 | * @return 文件内容的字节数组 21 | */ 22 | public abstract byte[] readFully() throws IOException; 23 | 24 | /** 25 | * 从文件offset开始处,随机读取length长度的字节数组 26 | * 27 | * @param offset 28 | * 开始读取的位置 29 | * @param length 30 | * 读取文件的长度 31 | * @return 读取到的字节数组 32 | */ 33 | public abstract byte[] seek(long offset, int size) throws IOException; 34 | 35 | /** 36 | * 将内存中的flush到磁盘中 37 | * 38 | * @param data 39 | * 内存中的字节数组 40 | */ 41 | public abstract void flush(byte[] data); 42 | 43 | /** 44 | * 从文件的末尾追加数据 45 | * 46 | * @param data 47 | * @return 追加完数据后 48 | */ 49 | public abstract InputOutData append(byte[] data) throws IOException; 50 | 51 | /** 52 | * 顺序读取文件 53 | * 54 | * @param size 读取文件的长度 55 | * @return 56 | */ 57 | public abstract byte[] readSequentially(int size) throws IOException; 58 | 59 | /** 60 | * 获取文件的offset 61 | * 62 | * @return 63 | */ 64 | public abstract long maxOffSet(); 65 | 66 | /** 67 | * 将文件的指针置于相应的位置 68 | * @param offset 69 | * @return 70 | */ 71 | public abstract InputOutData position(long offset) throws IOException; 72 | 73 | /** 74 | * 清除该文件磁盘的所有内容 75 | */ 76 | public abstract InputOutData deleteFile(); 77 | 78 | /** 79 | * 新建文件 80 | * @return 81 | */ 82 | public abstract InputOutData createNewFile() throws IOException; 83 | 84 | /** 85 | * 将该文件读写指针置于开头 86 | * @return 87 | */ 88 | public abstract void jumpHeader() throws IOException; 89 | 90 | public abstract int readBlock(byte[] block) throws IOException; 91 | 92 | /** 93 | * 修改文件的名称为 newName 94 | * @param newName 95 | * @return 96 | */ 97 | public abstract boolean reName(String newName); 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/store/Lock.java: -------------------------------------------------------------------------------- 1 | package org.herDB.store; 2 | 3 | public abstract class Lock { 4 | protected final static int INTERVAL = 1000; 5 | 6 | /** 7 | * 获取锁的方法,这里是一次尝试获取,若要多次尝试需写while循环 8 | * 9 | * @return 10 | */ 11 | protected abstract boolean acquire(); 12 | 13 | /** 14 | * 释放锁的方法,这里是一次释放就可以 15 | * 16 | * @return 17 | */ 18 | public abstract void release(); 19 | 20 | /** 21 | * 尝试多次获取锁,若经过仍没能获取成功就直接返回false 22 | * 23 | * @return 24 | * @throws Exception 25 | */ 26 | public boolean lock(int lockWaitTimes) { 27 | 28 | int times = 0; 29 | while (++times < lockWaitTimes) { 30 | if (acquire()) 31 | return true; 32 | try { 33 | Thread.sleep(INTERVAL); 34 | } catch (InterruptedException e) { 35 | e.printStackTrace(); 36 | return false; 37 | } 38 | } 39 | return false; 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/org/herDB/store/MMapInputStream.java: -------------------------------------------------------------------------------- 1 | package org.herDB.store; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.RandomAccessFile; 6 | import java.nio.MappedByteBuffer; 7 | import java.nio.channels.FileChannel; 8 | 9 | import org.herDB.herdb.UnsupportedException; 10 | 11 | 12 | /** 13 | * 为了加快文件随机读的操作,将文件映射到内存中 14 | * 注意: 不能在又读又写的情况下开启映射文件,为了性能映射内存只映射一次, 不能根据文件的追加而实时改变内存; 15 | * 16 | * @author funeyu 17 | */ 18 | public class MMapInputStream extends InputOutData { 19 | 20 | private MappedByteBuffer randomFile; 21 | 22 | @SuppressWarnings("resource") 23 | public MMapInputStream(File dir, String pathName) { 24 | File file = new File(dir, pathName); 25 | try { 26 | randomFile = new RandomAccessFile(file, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, 27 | file.length()).load(); 28 | } catch (IOException e) { 29 | e.printStackTrace(); 30 | } 31 | } 32 | 33 | // 不需要将磁盘文件全部读到内存中 34 | @Override 35 | public byte[] readFully() throws IOException { 36 | 37 | throw new UnsupportedException("readFully", this); 38 | } 39 | 40 | @Override 41 | public byte[] seek(long offset, int size) throws IOException { 42 | 43 | randomFile.position((int) offset); 44 | return null; 45 | } 46 | 47 | @Override 48 | public InputOutData append(byte[] data) throws IOException { 49 | 50 | throw new UnsupportedException("append", this); 51 | } 52 | 53 | @Override 54 | public void flush(byte[] data) { 55 | 56 | throw new UnsupportedException("flush", this); 57 | } 58 | 59 | @Override 60 | public byte[] readSequentially(int size) throws IOException { 61 | 62 | byte[] result = new byte[size]; 63 | randomFile.get(result); 64 | return result; 65 | } 66 | 67 | @Override 68 | public long maxOffSet() { 69 | 70 | return randomFile.capacity(); 71 | } 72 | 73 | @Override 74 | public InputOutData position(long offset) throws IOException { 75 | 76 | randomFile.position((int) offset); 77 | return this; 78 | } 79 | 80 | @Override 81 | public InputOutData deleteFile() { 82 | 83 | throw new UnsupportedException("deleteFile", this); 84 | } 85 | 86 | @Override 87 | public InputOutData createNewFile() throws IOException { 88 | 89 | throw new UnsupportedException("createNewFile", this); 90 | } 91 | 92 | @Override 93 | public int readBlock(byte[] block) { 94 | 95 | return randomFile.get(block).capacity(); 96 | } 97 | 98 | @Override 99 | public boolean reName(String newName) { 100 | 101 | throw new UnsupportedException("reName", this); 102 | } 103 | 104 | @Override 105 | public void jumpHeader() throws IOException { 106 | 107 | randomFile.position(0); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/utils/Bytes.java: -------------------------------------------------------------------------------- 1 | package org.herDB.utils; 2 | 3 | import java.util.Arrays; 4 | 5 | public final class Bytes { 6 | 7 | private Bytes() { 8 | } 9 | 10 | /** 11 | * 将数组the 与 other两个数组合并成一个数组并返回 12 | * 13 | * @param the 14 | * @param other 15 | * @return 16 | */ 17 | public final static byte[] join(byte[] the, byte[] other) { 18 | 19 | if (the == null) { 20 | return other; 21 | } 22 | if (other == null) { 23 | return the; 24 | } 25 | 26 | byte[] result = new byte[the.length + other.length]; 27 | 28 | for (int i = 0, length = the.length; i < length; i++) { 29 | result[i] = the[i]; 30 | } 31 | 32 | for (int i = 0, theLength = the.length, otherLength = other.length; i < otherLength; i++) { 33 | result[theLength + i] = other[i]; 34 | } 35 | 36 | return result; 37 | } 38 | 39 | /** 40 | * 将key 与 value打包成byte[]返回, 同时写入总长度
41 | * 格式为:datalength | keylength | key | value 42 | * 43 | * @param key 44 | * @param value 45 | * @return 46 | */ 47 | public final static byte[] wrapData(byte[] key, byte[] value) { 48 | 49 | // 先写入该wrapedData的长度 50 | byte[] datalength = NumberPacker.packInt(key.length + value.length + 4); 51 | // 写入key的长度 52 | byte[] keylength = NumberPacker.packInt(key.length); 53 | 54 | return join(join(join(datalength, keylength), key), value); 55 | } 56 | 57 | /** 58 | * 从一个byte[]整体里得出key的具体内容,resultBytes格式: 59 | *

offset(5 bytes) + itemlength(4 bytes) + keylen(4 bytes) + key/value(n bytes)
60 | * 61 | * @param resultBytes: Offset + itemData 62 | * @return key的bytes 63 | */ 64 | public final static byte[] extractKey(byte[] resultBytes) { 65 | int keyLength = NumberPacker.unpackInt(new byte[]{ 66 | resultBytes[9], 67 | resultBytes[10], 68 | resultBytes[11], 69 | resultBytes[12] 70 | }); 71 | 72 | byte[] key = new byte[keyLength]; 73 | for (int i = 0; i < keyLength; i++) { 74 | key[i] = resultBytes[13 + i]; 75 | } 76 | 77 | return key; 78 | } 79 | 80 | /** 81 | * 从resultBytes得出offset数据 82 | * 83 | * @param resultBytes 84 | * @return 85 | */ 86 | public final static long extractOffset(byte[] resultBytes) { 87 | 88 | return NumberPacker.unpackLong(new byte[]{ 89 | resultBytes[0], 90 | resultBytes[1], 91 | resultBytes[2], 92 | resultBytes[3], 93 | resultBytes[4] 94 | }); 95 | } 96 | 97 | /** 98 | * 从rsultsBytes得出itemData的数据 99 | * 100 | * @param resultBytes 101 | * @return 102 | */ 103 | public final static byte[] extractItemData(byte[] resultBytes) { 104 | 105 | return Arrays.copyOfRange(resultBytes, 5, resultBytes.length); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/utils/Hash.java: -------------------------------------------------------------------------------- 1 | package org.herDB.utils; 2 | 3 | import java.util.Arrays; 4 | 5 | public class Hash { 6 | private final static int segmentSize = 7; 7 | 8 | private Hash() { 9 | } 10 | 11 | public static int FNVHash1(byte[] data) { 12 | 13 | final int p = 16777619; 14 | int hash = (int) 2166136261L; 15 | for (byte b : data) 16 | hash = (hash ^ b) * p; 17 | hash += hash << 13; 18 | hash ^= hash >> 7; 19 | hash += hash << 3; 20 | hash ^= hash >> 17; 21 | hash += hash << 5; 22 | return Math.abs(hash); 23 | } 24 | 25 | /** 26 | * 计算key的hashcode,为了内存索引数据中的hashcode字段 == 0判断该slot是否为空; 27 | * 需要将key的hashcode全部转成不等于0的数据; 28 | * 29 | * @param key 30 | * @return 不为0的数据 31 | */ 32 | public static int KeyHash(byte[] key) { 33 | 34 | int hashcode = Arrays.hashCode(key); 35 | return hashcode == 0 ? 1 : hashcode; 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/herDB/utils/NumberPacker.java: -------------------------------------------------------------------------------- 1 | package org.herDB.utils; 2 | 3 | /** 4 | * number与byte array的互相转换的工具类 note:这里的转换int 与long型number都是non-negative的 5 | * 6 | * @author funeyu 7 | */ 8 | public final class NumberPacker { 9 | 10 | private NumberPacker() { 11 | } 12 | 13 | /** 14 | * 将long型数字打包成字节数组 15 | * 16 | * @param ba long pack成的字节数组 17 | * @param value 需要pack的long 18 | * @return 19 | */ 20 | public static byte[] packLong(long value) { 21 | 22 | byte[] ba = new byte[5]; 23 | if (value < 0) { 24 | throw new IllegalArgumentException("negative value: v=" + value); 25 | } 26 | 27 | int i = 1; 28 | while ((value & ~0x7FL) != 0) { 29 | ba[i - 1] = (byte) (((int) value & 0x7F) | 0x80); 30 | value >>>= 7; 31 | i++; 32 | } 33 | ba[i - 1] = (byte) value; 34 | return ba; 35 | } 36 | 37 | /** 38 | * 将一定的字节数组解包成long数字 39 | * 40 | * @param bytes 41 | * @return 42 | */ 43 | public static long unpackLong(byte[] bytes) { 44 | 45 | long result = 0; 46 | int index = 0; 47 | for (int offset = 0; offset < 64; offset += 7) { 48 | long b = bytes[index++]; 49 | result |= (b & 0x7F) << offset; 50 | if ((b & 0x80) == 0) { 51 | return result; 52 | } 53 | } 54 | throw new Error("Malformed long."); 55 | } 56 | 57 | /** 58 | * 将字符数组转换成int 59 | * 60 | * @param bytes 61 | * @return int 62 | */ 63 | public static int unpackInt(byte[] bytes) { 64 | 65 | int result = 0; 66 | for (int i = 0; i < 4; i++) { 67 | result |= ((bytes[i] & 0xFF) << (4 - 1 - i) * 8); 68 | } 69 | 70 | return result; 71 | } 72 | 73 | /** 74 | * 将int number转换成字节数组 75 | * 76 | * @param number 77 | * @return 长度为4的字节数组 78 | */ 79 | public static byte[] packInt(int value) { 80 | 81 | byte[] ba = new byte[4]; 82 | 83 | for (int i = 0; i < 4; i++) { 84 | ba[i] = (byte) ((value >> 8 * (4 - 1 - i)) & 0xFF); 85 | } 86 | return ba; 87 | } 88 | 89 | public static void main(String[] args) { 90 | byte[] bytes = packInt(698446115); 91 | System.out.println(unpackInt(bytes)); 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/test/getTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by fuheyu on 2018/6/8. 3 | */ 4 | import org.herDB.herdb.Configuration; 5 | import org.herDB.herdb.HerDB; 6 | import org.junit.Assert; 7 | import org.junit.BeforeClass; 8 | import org.junit.Test; 9 | 10 | /** 11 | * Created by fuheyu on 2018/6/8. 12 | */ 13 | public class getTest { 14 | 15 | 16 | @Test 17 | public void getTest() throws Exception { 18 | 19 | HerDB herDB = HerDB.openOnlyRead("herdb"); 20 | String v = herDB.get("java"); 21 | Assert.assertEquals("eclipse", v); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/main/test/putbyteTest.java: -------------------------------------------------------------------------------- 1 | import com.sun.deploy.util.SystemUtils; 2 | import org.herDB.herdb.Configuration; 3 | import org.herDB.herdb.HerDB; 4 | import org.junit.Assert; 5 | import org.junit.BeforeClass; 6 | import org.junit.Test; 7 | 8 | /** 9 | * Created by fuheyu on 2018/6/8. 10 | */ 11 | public class putbyteTest { 12 | 13 | private static Configuration config; 14 | private static HerDB herDB; 15 | 16 | @BeforeClass 17 | public static void setUpConfig() throws Exception { 18 | config = Configuration.create("herdb"); 19 | 20 | // 初始的情况下,slots的数组的大小 21 | config.set(Configuration.SLOTS_CAPACITY, "65536"); 22 | 23 | // 设置读写缓冲块的大小 24 | config.set(Configuration.BUFFERED_BLOCK_SIZE, "8192"); 25 | 26 | // 设置key/value数据的最大长度 27 | config.set(Configuration.ITEM_DATA_MAX_SIZE, "1024"); 28 | 29 | herDB = HerDB.create(config, "herdb"); 30 | } 31 | 32 | @Test 33 | public void putTest() { 34 | herDB.put("java", "eclipse"); 35 | herDB.put("javaddddddd", "iajofdja;jfoa"); 36 | herDB.commit(); 37 | } 38 | 39 | @Test 40 | public void putRate() { 41 | 42 | long start = System.currentTimeMillis(); 43 | 44 | for(int i = 0; i < 1024 * 1024 * 10; i ++) { 45 | herDB.put("java"+ i, "javajavassssss" + i); 46 | } 47 | 48 | herDB.commit(); 49 | 50 | long end = System.currentTimeMillis(); 51 | 52 | System.out.println("time:" + (end -start) / 1000 ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /target/classes/org/herDB/cache/StorageCache$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/cache/StorageCache$1.class -------------------------------------------------------------------------------- /target/classes/org/herDB/cache/StorageCache$NoCache.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/cache/StorageCache$NoCache.class -------------------------------------------------------------------------------- /target/classes/org/herDB/cache/StorageCache.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/cache/StorageCache.class -------------------------------------------------------------------------------- /target/classes/org/herDB/herdb/Configuration.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/herdb/Configuration.class -------------------------------------------------------------------------------- /target/classes/org/herDB/herdb/HerDB.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/herdb/HerDB.class -------------------------------------------------------------------------------- /target/classes/org/herDB/herdb/UnsupportedException.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/herdb/UnsupportedException.class -------------------------------------------------------------------------------- /target/classes/org/herDB/index/BufferedBlock.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/index/BufferedBlock.class -------------------------------------------------------------------------------- /target/classes/org/herDB/index/IndexMemoryByte.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/index/IndexMemoryByte.class -------------------------------------------------------------------------------- /target/classes/org/herDB/index/IndexOutofRangeException.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/index/IndexOutofRangeException.class -------------------------------------------------------------------------------- /target/classes/org/herDB/index/IndexSegment.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/index/IndexSegment.class -------------------------------------------------------------------------------- /target/classes/org/herDB/index/ReadingBufferedBlock.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/index/ReadingBufferedBlock.class -------------------------------------------------------------------------------- /target/classes/org/herDB/index/Slot.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/index/Slot.class -------------------------------------------------------------------------------- /target/classes/org/herDB/index/WritingBufferedBlock.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/index/WritingBufferedBlock.class -------------------------------------------------------------------------------- /target/classes/org/herDB/net/Client.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/net/Client.class -------------------------------------------------------------------------------- /target/classes/org/herDB/net/Commands.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/net/Commands.class -------------------------------------------------------------------------------- /target/classes/org/herDB/net/Server$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/net/Server$1.class -------------------------------------------------------------------------------- /target/classes/org/herDB/net/Server$Listenner.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/net/Server$Listenner.class -------------------------------------------------------------------------------- /target/classes/org/herDB/net/Server.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/net/Server.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/PrimitiveType$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/PrimitiveType$1.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/PrimitiveType$2.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/PrimitiveType$2.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/PrimitiveType$3.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/PrimitiveType$3.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/PrimitiveType$4.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/PrimitiveType$4.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/PrimitiveType$5.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/PrimitiveType$5.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/PrimitiveType$6.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/PrimitiveType$6.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/PrimitiveType.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/PrimitiveType.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/Reflection.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/Reflection.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/Serializer.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/Serializer.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/SerializerImp$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/SerializerImp$1.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/SerializerImp$StringSerializer.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/SerializerImp$StringSerializer.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/SerializerImp.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/SerializerImp.class -------------------------------------------------------------------------------- /target/classes/org/herDB/serializer/SerializerUtils.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/serializer/SerializerUtils.class -------------------------------------------------------------------------------- /target/classes/org/herDB/store/FSDirectory$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/store/FSDirectory$1.class -------------------------------------------------------------------------------- /target/classes/org/herDB/store/FSDirectory$FSDataStream.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/store/FSDirectory$FSDataStream.class -------------------------------------------------------------------------------- /target/classes/org/herDB/store/FSDirectory.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/store/FSDirectory.class -------------------------------------------------------------------------------- /target/classes/org/herDB/store/InputOutData.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/store/InputOutData.class -------------------------------------------------------------------------------- /target/classes/org/herDB/store/Lock.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/store/Lock.class -------------------------------------------------------------------------------- /target/classes/org/herDB/store/MMapInputStream.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/store/MMapInputStream.class -------------------------------------------------------------------------------- /target/classes/org/herDB/utils/Bytes.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/utils/Bytes.class -------------------------------------------------------------------------------- /target/classes/org/herDB/utils/Hash.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/utils/Hash.class -------------------------------------------------------------------------------- /target/classes/org/herDB/utils/NumberPacker.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/classes/org/herDB/utils/NumberPacker.class -------------------------------------------------------------------------------- /target/test-classes/getTest.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/test-classes/getTest.class -------------------------------------------------------------------------------- /target/test-classes/putbyteTest.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funeyu/herDB/7f6dd64db2c806cb3ef8bbf88e72600c8a31283a/target/test-classes/putbyteTest.class --------------------------------------------------------------------------------