MapDB使用入门
背景
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包
<!-- https://mvnrepository.com/artifact/org.mapdb/mapdb -->
<dependency>
<groupId>org.mapdb</groupId>
<artifactId>mapdb</artifactId>
<version>3.0.7</version>
</dependency>
2、基于堆外存储的Hello,Simple
/**
* 堆外内存map
*/
public static void offHeapMapTest1() {
DB db = DBMaker.memoryDB().make();
ConcurrentMap map = db.hashMap("map").createOrOpen();
String key = "Hello";
String val = "simple";
map.put(key, val);
System.out.println("第1次取值," + map.get(key));
}
执行结果:
--Hello,simple----
第1次取值,simple
2.1、插曲
刚开始执行的时候,总是报下面的异常
Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/collections/impl/list/mutable/primitive/LongArrayList
at org.mapdb.StoreDirectAbstract.<init>(StoreDirectAbstract.kt:41)
at org.mapdb.StoreDirect.<init>(StoreDirect.kt:30)
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.HelloWorldDemo.offHeapMapTest1(HelloWorldDemo.java:18)
at me.lovegao.mapdb.hello.HelloWorldDemo.main(HelloWorldDemo.java:11)
Caused by: java.lang.ClassNotFoundException: org.eclipse.collections.impl.list.mutable.primitive.LongArrayList
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 7 more
我以为是jar包没加全,又加了Eclipse相关的jar包,发现还是这样。对着项目又是清理,又是重新编译,又是重新导包,发现都不行。
仔细看了maven的依赖,都是存在的,看MapDB的文档,也说上面那个配置包含全部的。
最后,索性把本地相关的jar包都删了,让maven重新下,最终正常了。
3、基于磁盘的Hello,Simple
基于磁盘存储的,为了保证数据的完整性,需要在关闭虚拟机前关闭DB。
public static void fileMapTest1() {
DB db = DBMaker.fileDB("file.db").make();
ConcurrentMap map = db.hashMap("map").createOrOpen();
String key = "something";
String val = "here";
map.put(key, val);
System.out.println("第1次取值," +map.get(key));
db.close();
System.out.println("----------重新打开----------");
db = DBMaker.fileDB("file.db").make();
map = db.hashMap("map").createOrOpen();
System.out.println("第2次取值," +map.get(key));
db.close();
}
执行结果:
--Hello,simple----
第1次取值,simple
----------重新打开----------
第2次取值,simple
结果符合预期。
3.1、基于磁盘的,内存映射的使用
/**
* 在64位操作系统中,开启内存映射
* 个性化序列化
*/
public static void fileMapMemoryMapTest() {
DB db = DBMaker
.fileDB("file.db")
.fileMmapEnable()
.make();
ConcurrentMap<String,Long> map = db
.hashMap("mapsl", Serializer.STRING, Serializer.LONG)
.createOrOpen();
long val = 51;
map.put(DEMO_KEY, val);
System.out.println("第1次取值,期望值:" + val + ",取到的值:" +map.get(DEMO_KEY));
db.close();
db = DBMaker
.fileDB("file.db")
.fileMmapEnable()
.make();
map = db.hashMap("mapsl", Serializer.STRING, Serializer.LONG)
.createOrOpen();
System.out.println("第2次取值,期望值:" + val + ",取到的值:" +map.get(DEMO_KEY));
db.close();
}
执行结果:
--Hello,simple----
第1次取值,期望值:51,取到的值:51
第2次取值,期望值:51,取到的值:51
4、性能对比
1)测试代码
package me.lovegao.mapdb.hello; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap; import org.eclipse.collections.impl.map.mutable.ConcurrentHashMap;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.mapdb.Serializer; public class MapDBSpeedTest {
private final static String DEMO_KEY = "Hello";
private final static String DEMO_VAL = "simple"; public static void main(String[] args) {
System.out.println("--Hello,simple----");
// fileMapMemoryMapTest();
mapTest();
} public static void mapTest() {
int dataNum = 10000;
List<DB> dbList = new ArrayList();
Map<String, Map<String, Long>> testMap = new HashMap();
Map<String, Long> dataMap = generateTestData(dataNum);
//java原生-堆内map
ConcurrentMap<String, Long> inHeapDbMap = new ConcurrentHashMap();
testMap.put("原生map", inHeapDbMap); //堆外map
DB offHeapDb = DBMaker.memoryDB().make();
dbList.add(offHeapDb);
ConcurrentMap offHeapDbMap = offHeapDb.hashMap("map").createOrOpen();
testMap.put("堆外map", offHeapDbMap); //基于磁盘map
DB fileDb = DBMaker.fileDB("file1.db").make();
dbList.add(fileDb);
ConcurrentMap<String,Long> fileDbMap = fileDb
.hashMap("map1", Serializer.STRING, Serializer.LONG)
.createOrOpen();
testMap.put("基于磁盘map", fileDbMap); //基于磁盘-内存映射map
DB fileMmapDb = DBMaker
.fileDB("file2.db")
.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
.fileMmapEnable() // Always enable mmap
// .fileMmapEnableIfSupported() // Only enable mmap on supported platforms,对性能影响较大
.fileMmapPreclearDisable() // Make mmap file faster
// .allocateStartSize( 10 * 1024*1024*1024) // 10GB,初始容量
// .allocateIncrement(512 * 1024*1024) // 512MB,每次增加容量
.make();
//optionally preload file content into disk cache
fileMmapDb.getStore().fileLoad();
dbList.add(fileMmapDb);
ConcurrentMap<String,Long> fileMmapMap = fileMmapDb
.hashMap("map2", Serializer.STRING, Serializer.LONG)
.createOrOpen();
testMap.put("基于磁盘-内存映射map", fileMmapMap); System.out.println("-----------put---数据量:"+dataNum+"------");
for(String mapType : testMap.keySet()) {
putGetMapTest(mapType, testMap.get(mapType), dataMap, true);
}
System.out.println("-----------------------------------------\n");
System.out.println("-----------get---数据量:"+dataNum+"------");
for(String mapType : testMap.keySet()) {
putGetMapTest(mapType, testMap.get(mapType), dataMap, false);
}
for(DB db : dbList) {
db.close();
}
} /**
* putGet测试
* @param map
* @param dataMap
* @param put
* @return <耗时, 异常数>
*/
public static TwoTuple<Long, Long> putGetMapTest(String mapType, Map<String, Long> map, Map<String, Long> dataMap, boolean put) {
long useTime = 0L;
long errorNum = 0L;
Iterator<Entry<String, Long>> entryIt = dataMap.entrySet().iterator();
while(entryIt.hasNext()) {
Entry<String, Long> entry = entryIt.next();
if(put) {
long t1 = System.nanoTime();
map.put(entry.getKey(), entry.getValue());
useTime = System.nanoTime() - t1;
} else {
long t1 = System.nanoTime();
long val = map.get(entry.getKey());
useTime = System.nanoTime() - t1;
if(val != entry.getValue()) {
errorNum++;
}
}
}
double avgUseTime = (double)useTime / dataMap.size();
String fmtStr = "map类型:%s,总耗时:%dns,平均耗时%ens,异常数量:%d";
System.out.println(String.format(fmtStr, mapType, useTime, avgUseTime, errorNum));
return new TwoTuple<Long, Long>(useTime, errorNum);
} /**
* 生成测试数据
* @param size
* @return
*/
public static Map<String, Long> generateTestData(int size) {
Map<String, Long> map = new HashMap();
int arrLength = 26;
char[] words = new char[arrLength];
for(int i=0; i<arrLength; i++) {
words[i] = (char) ('a' + i);
}
System.out.println(words);
String demoWord = new String(words);
for(int i=0; i<size; i++) {
String key = demoWord.substring(i%arrLength, i%arrLength) + i;
long val = i;
map.put(key, val);
}
return map;
} /**
* 对外内存map
*/
public static void offHeapMapTest1() {
DB db = DBMaker.memoryDB().make();
ConcurrentMap map = db.hashMap("map").createOrOpen();
map.put(DEMO_KEY, DEMO_VAL);
System.out.println("第1次取值," + map.get(DEMO_KEY));
} /**
* 基于磁盘的存储
*/
public static void fileMapTest1() {
DB db = DBMaker.fileDB("file.db").make();
ConcurrentMap map = db.hashMap("map").createOrOpen(); map.put(DEMO_KEY, DEMO_VAL);
System.out.println("第1次取值," +map.get(DEMO_KEY));
db.close();
System.out.println("----------重新打开----------");
db = DBMaker.fileDB("file.db").make();
map = db.hashMap("map").createOrOpen();
System.out.println("第2次取值," +map.get(DEMO_KEY));
db.close();
} /**
* 在64位操作系统中,开启内存映射
* 个性化序列化
*/
public static void fileMapMemoryMapTest() {
DB db = DBMaker
.fileDB("file.db")
.fileMmapEnable()
.make();
ConcurrentMap<String,Long> map = db
.hashMap("mapsl", Serializer.STRING, Serializer.LONG)
.createOrOpen();
long val = 51;
map.put(DEMO_KEY, val);
System.out.println("第1次取值,期望值:" + val + ",取到的值:" +map.get(DEMO_KEY));
db.close();
db = DBMaker
.fileDB("file.db")
.fileMmapEnable()
.make();
map = db.hashMap("mapsl", Serializer.STRING, Serializer.LONG)
.createOrOpen();
System.out.println("第2次取值,期望值:" + val + ",取到的值:" +map.get(DEMO_KEY));
db.close();
} }
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是支持事务的,具体使用如下:
package me.lovegao.mapdb.hello; import java.util.concurrent.ConcurrentMap; import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.mapdb.Serializer; public class MapDBTransaction { public static void main(String[] args) {
DB db = DBMaker
.fileDB("file3.db")
.fileMmapEnable()
.transactionEnable()
.closeOnJvmShutdown() //JVM关闭时关闭db
.make();
ConcurrentMap<String,Long> map = db
.hashMap("mapsl3", Serializer.STRING, Serializer.LONG)
.createOrOpen();
map.put("a", 1L);
map.put("b", 2L);
db.commit();
System.out.println(map.get("a"));
System.out.println(map.get("b"));
map.put("c", 3L);
System.out.println("rollback之前,c:" + map.get("c"));
db.rollback();
System.out.println("rollback之后,a:" + map.get("a"));
System.out.println("rollback之后,c:" + map.get("c"));
} }
运行结果:
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自动创建的,切记不要多此一举,把文件也创建了,那样会报错的。
MapDB使用入门的更多相关文章
- Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求
上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...
- ABP入门系列(1)——学习Abp框架之实操演练
作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...
- Oracle分析函数入门
一.Oracle分析函数入门 分析函数是什么?分析函数是Oracle专门用于解决复杂报表统计需求的功能强大的函数,它可以在数据中进行分组然后计算基于组的某种统计值,并且每一组的每一行都可以返回一个统计 ...
- Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数
上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...
- Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数
上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...
- Angular2入门系列教程4-服务
上一篇文章 Angular2入门系列教程-多个组件,主从关系 在编程中,我们通常会将数据提供单独分离出来,以免在编写程序的过程中反复复制粘贴数据请求的代码 Angular2中提供了依赖注入的概念,使得 ...
- wepack+sass+vue 入门教程(三)
十一.安装sass文件转换为css需要的相关依赖包 npm install --save-dev sass-loader style-loader css-loader loader的作用是辅助web ...
- wepack+sass+vue 入门教程(二)
六.新建webpack配置文件 webpack.config.js 文件整体框架内容如下,后续会详细说明每个配置项的配置 webpack.config.js直接放在项目demo目录下 module.e ...
- wepack+sass+vue 入门教程(一)
一.安装node.js node.js是基础,必须先安装.而且最新版的node.js,已经集成了npm. 下载地址 node安装,一路按默认即可. 二.全局安装webpack npm install ...
随机推荐
- 聊聊HTML5中的Web Notification桌面通知
有的时候我们会在桌面右下角看到这样的提示: 这种桌面提示是HTML5新增的 Web Push Notifications 技术. Web Notifications 技术使页面可以发出通知,通知将被显 ...
- 使用钉钉对接禅道的bug系统,实现禅道提的bug实时在钉钉提醒并艾特对应的开发人员处理
现在公司测试中有一个痛点是每次测试人员提完bug后,需要定期去提醒开发人员查看禅道的bug记录及修复bug. 导致测试人员在项目测试中不仅要测试整个软件,还要负起实时监督提醒功能的“保姆角色”,身心疲 ...
- Java的自动装箱/拆箱
概述 自JDK1.5开始, 引入了自动装箱/拆箱这一语法糖, 它使程序员的代码变得更加简洁, 不再需要进行显式转换.基本类型与包装类型在某些操作符的作用下, 包装类型调用valueOf()方法将原始类 ...
- 有容云-PPT | 当微服务遇见容器
编者注: 本文为10月29日有容云高级技术顾问龙淼在Docker Live时代线下系列-广州站中演讲的PPT,本次线下沙龙为有容云倾力打造Docker Live时代系列主题线下沙龙,每月一期畅聊容器技 ...
- spring学习笔记之---bean属性注入
bean属性注入 (一)构造方法的属性注入 1.Student.java package entity; public class Student { private String name; pri ...
- WinForm开发中通用附件管理控件设计开发参考
1.引言 在WinForm开发中,文件附件的管理几乎在任何一个应用上都会存在,是一个非常通用集中的公共模块.我们日常记录会伴随着有图片.文档等附件形式来展现,如果为每个业务对象都做一个附件管理,或者每 ...
- 在 树莓派(Raspberry PI) 中使用 Docker 运行 aspnetcore/dotnetcore 应用
本文主要利用 Microsoft 提供的 Dockerfile 进行安装. 虽然Raspberry PI 3 CPU支持 armv8 指令集 ,但是在 docker info 还是识别为 " ...
- 消息中间件-activemq实战之消息持久化(六)
对于activemq消息的持久化我们在第二节的时候就简单介绍过,今天我们详细的来分析一下activemq的持久化过程以及持久化插件.在生产环境中为确保消息的可靠性,我们肯定的面临持久化消息的问题,今天 ...
- X-Admin&ABP框架开发-设置管理
在网站开发中,设置是不可缺少的一环,如用户设置.系统设置.甚至是租户设置等.ABP对于设置的管理已经做了很好的处理,我们可以借助巨人的力量来完成我们的冒险. ABP官网地址:https://aspne ...
- Java.基础 -------- 一个Java源文件中可以有很多类,但只能有一个类是public的
一个Java源文件中可以有很多类,但只能有一个类是public的 Java程序是从一个public类main函数开始执行的,只能有一个public是为了给类装载器提供方便,一个publi ...