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 ...
随机推荐
- python课堂整理10---局部变量与全局变量
一.局部变量与全局变量 1. 没有缩进,顶头写的变量为全局变量 2. 在子程序里定义的变量为局部变量 3. 只有函数能把变量私有化 name = 'lhf' #全局变量 def change_name ...
- 这 3 个 Set 集合的实现有点简单,那来做个总结吧
Set 接口是 Java Collections Framework 中的一员,它的特点是:不能包含重复的元素,允许且最多只有一个 null 元素.Java 中有三个常用的 Set 实现类: Hash ...
- PAY8 数字货币支付结算系统,全球付!实时结算!秒到账!
数字货币支付是历史发展的必然 如今已经有越来越多的地方接受加密数字货币作为支付消费了,比如泰国电影院连锁店 Cineplex Group 可用加密货币买爆米花和电影票,西班牙一精品酒店接受数字货币支付 ...
- 【iOS】iOS Error Domain=NSCocoaErrorDomain Code=3840 "未能完成操作。(“Cocoa”错误 3840。)"
昨天遇到的这个问题,详细信息: ----->类和方法__25+[Manager noticeRequest:]_block_invoke399----->错误信息Error Domain= ...
- spring boot中的声明式事务管理及编程式事务管理
这几天在做一个功能,具体的情况是这样的: 项目中原有的几个功能模块中有数据上报的功能,现在需要在这几个功能模块的上报之后生成一条消息记录,然后入库,在写个接口供前台来拉取消息记录. 看到这个需求,首先 ...
- 表单与v-model
基本用法 vue.js提供了v-model指令,用于在表单类元素上双向绑定数据,例如在输入框上使用,输入的内容会实时映射到绑定的数据上 example1:修改input框内容,message会实时渲染 ...
- Android 虹软人脸识别SDK-人脸对比
准备 : 登录官方网站,获取SDK,进行个人验证后新建项目,获取APP_ID,和SDK_KEY: https://ai.arcsoft.com.cn/ucenter/resource/build/in ...
- 【Java例题】6.1 进制转换
1.进制转换.输入一个某种进制的整数,将其转换为其它进制的整数.说明:仅考虑十.二.八和十六进制. package chapter6; import java.util.*; public class ...
- Serilog 自定义Enricher 来增加记录的信息
Serilog 自定义Enricher 来增加记录的信息 Intro Serilog 是 .net 里面非常不错的记录日志的库,结构化日志记录,而且配置起来很方便,自定义扩展也很方便 Serilog ...
- Go中的反射reflect
前面我们在学习到struct结构体的时候,因为结构体中的字段首字母大写,而我们想把json文件映射到该结构体上时,需要在在结构体字段后面加上json标签,表明结构体字段和json字段的映射关系.这其中 ...