经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
MapDB使用入门
来源:cnblogs  作者:水木桶  时间:2019/9/18 8:45:33  对本文有异议

背景

MapDB官网:http://www.mapdb.org

官方翻译之后的话:MapDB基于堆外存储、磁盘存储提供了Java的Maps、Sets、Lists、Queues等功能。它混合了Java集合框架和数据库引擎。它是基于Apache许可的免费的、开源的。

个人觉得:MapDB是一个轻量级的本地缓存的框架,它既可以使用对外存储,也可以使用磁盘存储(重启时数据不丢失)。它还提供事务的功能。

开发文档:https://jankotek.gitbooks.io/mapdb/content/quick-start/

开发机器配置:i5-9400 6c6t,32g内存,固态硬盘

MapDB入门实战

1、引入jar包

  1. <!-- https://mvnrepository.com/artifact/org.mapdb/mapdb -->
  2. <dependency>
  3. <groupId>org.mapdb</groupId>
  4. <artifactId>mapdb</artifactId>
  5. <version>3.0.7</version>
  6. </dependency>

2、基于堆外存储的Hello,Simple

  1. /**
  2. * 堆外内存map
  3. */
  4. public static void offHeapMapTest1() {
  5. DB db = DBMaker.memoryDB().make();
  6. ConcurrentMap map = db.hashMap("map").createOrOpen();
  7. String key = "Hello";
  8. String val = "simple";
  9. map.put(key, val);
  10. System.out.println("第1次取值," + map.get(key));
  11. }

执行结果:

--Hello,simple----
第1次取值,simple

2.1、插曲

刚开始执行的时候,总是报下面的异常

  1. Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/collections/impl/list/mutable/primitive/LongArrayList
  2. at org.mapdb.StoreDirectAbstract.<init>(StoreDirectAbstract.kt:41)
  3. at org.mapdb.StoreDirect.<init>(StoreDirect.kt:30)
  4. at org.mapdb.StoreDirect$Companion.make(StoreDirect.kt:57)
  5. at org.mapdb.StoreDirect$Companion.make$default(StoreDirect.kt:56)
  6. at org.mapdb.DBMaker$Maker.make(DBMaker.kt:450)
  7. at me.lovegao.mapdb.hello.HelloWorldDemo.offHeapMapTest1(HelloWorldDemo.java:18)
  8. at me.lovegao.mapdb.hello.HelloWorldDemo.main(HelloWorldDemo.java:11)
  9. Caused by: java.lang.ClassNotFoundException: org.eclipse.collections.impl.list.mutable.primitive.LongArrayList
  10. at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
  11. at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
  12. at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
  13. at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
  14. ... 7 more

 我以为是jar包没加全,又加了Eclipse相关的jar包,发现还是这样。对着项目又是清理,又是重新编译,又是重新导包,发现都不行。

仔细看了maven的依赖,都是存在的,看MapDB的文档,也说上面那个配置包含全部的。

最后,索性把本地相关的jar包都删了,让maven重新下,最终正常了。

3、基于磁盘的Hello,Simple

基于磁盘存储的,为了保证数据的完整性,需要在关闭虚拟机前关闭DB。

  1. public static void fileMapTest1() {
  2. DB db = DBMaker.fileDB("file.db").make();
  3. ConcurrentMap map = db.hashMap("map").createOrOpen();
  4. String key = "something";
  5. String val = "here";
  6. map.put(key, val);
  7. System.out.println("第1次取值," +map.get(key));
  8. db.close();
  9. System.out.println("----------重新打开----------");
  10. db = DBMaker.fileDB("file.db").make();
  11. map = db.hashMap("map").createOrOpen();
  12. System.out.println("第2次取值," +map.get(key));
  13. db.close();
  14. }

执行结果:

--Hello,simple----
第1次取值,simple
----------重新打开----------
第2次取值,simple

结果符合预期。

3.1、基于磁盘的,内存映射的使用

  1. /**
  2. * 在64位操作系统中,开启内存映射
  3. * 个性化序列化
  4. */
  5. public static void fileMapMemoryMapTest() {
  6. DB db = DBMaker
  7. .fileDB("file.db")
  8. .fileMmapEnable()
  9. .make();
  10. ConcurrentMap<String,Long> map = db
  11. .hashMap("mapsl", Serializer.STRING, Serializer.LONG)
  12. .createOrOpen();
  13. long val = 51;
  14. map.put(DEMO_KEY, val);
  15. System.out.println("第1次取值,期望值:" + val + ",取到的值:" +map.get(DEMO_KEY));
  16. db.close();
  17. db = DBMaker
  18. .fileDB("file.db")
  19. .fileMmapEnable()
  20. .make();
  21. map = db.hashMap("mapsl", Serializer.STRING, Serializer.LONG)
  22. .createOrOpen();
  23. System.out.println("第2次取值,期望值:" + val + ",取到的值:" +map.get(DEMO_KEY));
  24. db.close();
  25. }

 执行结果:

--Hello,simple----
第1次取值,期望值:51,取到的值:51
第2次取值,期望值:51,取到的值:51

4、性能对比

1)测试代码

  1. package me.lovegao.mapdb.hello;
  2. import java.util.ArrayList;
  3. import java.util.HashMap;
  4. import java.util.Iterator;
  5. import java.util.List;
  6. import java.util.Map;
  7. import java.util.Map.Entry;
  8. import java.util.concurrent.ConcurrentMap;
  9. import org.eclipse.collections.impl.map.mutable.ConcurrentHashMap;
  10. import org.mapdb.DB;
  11. import org.mapdb.DBMaker;
  12. import org.mapdb.Serializer;
  13. public class MapDBSpeedTest {
  14. private final static String DEMO_KEY = "Hello";
  15. private final static String DEMO_VAL = "simple";
  16. public static void main(String[] args) {
  17. System.out.println("--Hello,simple----");
  18. // fileMapMemoryMapTest();
  19. mapTest();
  20. }
  21. public static void mapTest() {
  22. int dataNum = 10000;
  23. List<DB> dbList = new ArrayList();
  24. Map<String, Map<String, Long>> testMap = new HashMap();
  25. Map<String, Long> dataMap = generateTestData(dataNum);
  26. //java原生-堆内map
  27. ConcurrentMap<String, Long> inHeapDbMap = new ConcurrentHashMap();
  28. testMap.put("原生map", inHeapDbMap);
  29. //堆外map
  30. DB offHeapDb = DBMaker.memoryDB().make();
  31. dbList.add(offHeapDb);
  32. ConcurrentMap offHeapDbMap = offHeapDb.hashMap("map").createOrOpen();
  33. testMap.put("堆外map", offHeapDbMap);
  34. //基于磁盘map
  35. DB fileDb = DBMaker.fileDB("file1.db").make();
  36. dbList.add(fileDb);
  37. ConcurrentMap<String,Long> fileDbMap = fileDb
  38. .hashMap("map1", Serializer.STRING, Serializer.LONG)
  39. .createOrOpen();
  40. testMap.put("基于磁盘map", fileDbMap);
  41. //基于磁盘-内存映射map
  42. DB fileMmapDb = DBMaker
  43. .fileDB("file2.db")
  44. .fileChannelEnable() //By default MapDB uses RandomAccessFile to access disk storage. Outside fast mmap files there is third option based on FileChannel. It should be faster than RandomAccessFile
  45. .fileMmapEnable() // Always enable mmap
  46. // .fileMmapEnableIfSupported() // Only enable mmap on supported platforms,对性能影响较大
  47. .fileMmapPreclearDisable() // Make mmap file faster
  48. // .allocateStartSize( 10 * 1024*1024*1024) // 10GB,初始容量
  49. // .allocateIncrement(512 * 1024*1024) // 512MB,每次增加容量
  50. .make();
  51. //optionally preload file content into disk cache
  52. fileMmapDb.getStore().fileLoad();
  53. dbList.add(fileMmapDb);
  54. ConcurrentMap<String,Long> fileMmapMap = fileMmapDb
  55. .hashMap("map2", Serializer.STRING, Serializer.LONG)
  56. .createOrOpen();
  57. testMap.put("基于磁盘-内存映射map", fileMmapMap);
  58. System.out.println("-----------put---数据量:"+dataNum+"------");
  59. for(String mapType : testMap.keySet()) {
  60. putGetMapTest(mapType, testMap.get(mapType), dataMap, true);
  61. }
  62. System.out.println("-----------------------------------------\n");
  63. System.out.println("-----------get---数据量:"+dataNum+"------");
  64. for(String mapType : testMap.keySet()) {
  65. putGetMapTest(mapType, testMap.get(mapType), dataMap, false);
  66. }
  67. for(DB db : dbList) {
  68. db.close();
  69. }
  70. }
  71. /**
  72. * putGet测试
  73. * @param map
  74. * @param dataMap
  75. * @param put
  76. * @return <耗时, 异常数>
  77. */
  78. public static TwoTuple<Long, Long> putGetMapTest(String mapType, Map<String, Long> map, Map<String, Long> dataMap, boolean put) {
  79. long useTime = 0L;
  80. long errorNum = 0L;
  81. Iterator<Entry<String, Long>> entryIt = dataMap.entrySet().iterator();
  82. while(entryIt.hasNext()) {
  83. Entry<String, Long> entry = entryIt.next();
  84. if(put) {
  85. long t1 = System.nanoTime();
  86. map.put(entry.getKey(), entry.getValue());
  87. useTime = System.nanoTime() - t1;
  88. } else {
  89. long t1 = System.nanoTime();
  90. long val = map.get(entry.getKey());
  91. useTime = System.nanoTime() - t1;
  92. if(val != entry.getValue()) {
  93. errorNum++;
  94. }
  95. }
  96. }
  97. double avgUseTime = (double)useTime / dataMap.size();
  98. String fmtStr = "map类型:%s,总耗时:%dns,平均耗时%ens,异常数量:%d";
  99. System.out.println(String.format(fmtStr, mapType, useTime, avgUseTime, errorNum));
  100. return new TwoTuple<Long, Long>(useTime, errorNum);
  101. }
  102. /**
  103. * 生成测试数据
  104. * @param size
  105. * @return
  106. */
  107. public static Map<String, Long> generateTestData(int size) {
  108. Map<String, Long> map = new HashMap();
  109. int arrLength = 26;
  110. char[] words = new char[arrLength];
  111. for(int i=0; i<arrLength; i++) {
  112. words[i] = (char) ('a' + i);
  113. }
  114. System.out.println(words);
  115. String demoWord = new String(words);
  116. for(int i=0; i<size; i++) {
  117. String key = demoWord.substring(i%arrLength, i%arrLength) + i;
  118. long val = i;
  119. map.put(key, val);
  120. }
  121. return map;
  122. }
  123. /**
  124. * 对外内存map
  125. */
  126. public static void offHeapMapTest1() {
  127. DB db = DBMaker.memoryDB().make();
  128. ConcurrentMap map = db.hashMap("map").createOrOpen();
  129. map.put(DEMO_KEY, DEMO_VAL);
  130. System.out.println("第1次取值," + map.get(DEMO_KEY));
  131. }
  132. /**
  133. * 基于磁盘的存储
  134. */
  135. public static void fileMapTest1() {
  136. DB db = DBMaker.fileDB("file.db").make();
  137. ConcurrentMap map = db.hashMap("map").createOrOpen();
  138. map.put(DEMO_KEY, DEMO_VAL);
  139. System.out.println("第1次取值," +map.get(DEMO_KEY));
  140. db.close();
  141. System.out.println("----------重新打开----------");
  142. db = DBMaker.fileDB("file.db").make();
  143. map = db.hashMap("map").createOrOpen();
  144. System.out.println("第2次取值," +map.get(DEMO_KEY));
  145. db.close();
  146. }
  147. /**
  148. * 在64位操作系统中,开启内存映射
  149. * 个性化序列化
  150. */
  151. public static void fileMapMemoryMapTest() {
  152. DB db = DBMaker
  153. .fileDB("file.db")
  154. .fileMmapEnable()
  155. .make();
  156. ConcurrentMap<String,Long> map = db
  157. .hashMap("mapsl", Serializer.STRING, Serializer.LONG)
  158. .createOrOpen();
  159. long val = 51;
  160. map.put(DEMO_KEY, val);
  161. System.out.println("第1次取值,期望值:" + val + ",取到的值:" +map.get(DEMO_KEY));
  162. db.close();
  163. db = DBMaker
  164. .fileDB("file.db")
  165. .fileMmapEnable()
  166. .make();
  167. map = db.hashMap("mapsl", Serializer.STRING, Serializer.LONG)
  168. .createOrOpen();
  169. System.out.println("第2次取值,期望值:" + val + ",取到的值:" +map.get(DEMO_KEY));
  170. db.close();
  171. }
  172. }

2)测试结果

map类型 测试数据量 测试类型 总耗时(ns) 平均耗时(ns)
原生map 10000 put 100 1.000000e-02
堆外map 同上 put 4800 4.800000e-01
基于磁盘map 同上 put 345000 3.450000e+01
基于磁盘-内存映射map 同上 put 6000 6.000000e-01
原生map 同上 get 100 1.000000e-02
堆外map 同上 get 2000 2.000000e-01
基于磁盘map 同上 get 75400 7.540000e+00
基于磁盘-内存映射map 同上 get 1100 1.100000e-01

 3)结论

①原生的基于堆的map速度始终是最快的

②堆外map和基于磁盘且开启了内存映射的map相比,优势较小。至于原因,有待深入理解。

③对于“基于磁盘-内存映射map”,使用“fileMmapEnableIfSupported”配置,对性能影响较大,建议直接开启。配置“fileMmapPreclearDisable”对于put的性能提升较大(约一倍提升)。

MapDB事务

MapDB是支持事务的,具体使用如下:

  1. package me.lovegao.mapdb.hello;
  2. import java.util.concurrent.ConcurrentMap;
  3. import org.mapdb.DB;
  4. import org.mapdb.DBMaker;
  5. import org.mapdb.Serializer;
  6. public class MapDBTransaction {
  7. public static void main(String[] args) {
  8. DB db = DBMaker
  9. .fileDB("file3.db")
  10. .fileMmapEnable()
  11. .transactionEnable()
  12. .closeOnJvmShutdown() //JVM关闭时关闭db
  13. .make();
  14. ConcurrentMap<String,Long> map = db
  15. .hashMap("mapsl3", Serializer.STRING, Serializer.LONG)
  16. .createOrOpen();
  17. map.put("a", 1L);
  18. map.put("b", 2L);
  19. db.commit();
  20. System.out.println(map.get("a"));
  21. System.out.println(map.get("b"));
  22. map.put("c", 3L);
  23. System.out.println("rollback之前,c:" + map.get("c"));
  24. db.rollback();
  25. System.out.println("rollback之后,a:" + map.get("a"));
  26. System.out.println("rollback之后,c:" + map.get("c"));
  27. }
  28. }

运行结果:

1
2
rollback之前,c:3
rollback之后,a:1
rollback之后,c:null

因为配置了closeOnJvmShutdown,所以再次运行时能够正常运行。

如果去掉了transactionEnable和closeOnJvmShutdown,再次运行时将出现以下异常:

Exception in thread "main" org.mapdb.DBException$DataCorruption: Header checksum broken. Store was not closed correctly and might be corrupted. Use `DBMaker.checksumHeaderBypass()` to recover your data. Use clean shutdown or enable transactions to protect the store in the future.
at org.mapdb.StoreDirectAbstract.fileHeaderCheck(StoreDirectAbstract.kt:113)
at org.mapdb.StoreDirect.<init>(StoreDirect.kt:114)
at org.mapdb.StoreDirect$Companion.make(StoreDirect.kt:57)
at org.mapdb.StoreDirect$Companion.make$default(StoreDirect.kt:56)
at org.mapdb.DBMaker$Maker.make(DBMaker.kt:450)
at me.lovegao.mapdb.hello.MapDBTransaction.main(MapDBTransaction.java:17)

最后说以下,fileDB("file3.db")这里的路径可以指定其他目录,默认是在项目的根目录下。

路径是自己创建的,文件是MapDB自动创建的,切记不要多此一举,把文件也创建了,那样会报错的。

原文链接:http://www.cnblogs.com/shuimutong/p/11438216.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号