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 ...
随机推荐
- libvirt-qemu-TLS加密虚拟机传输实例分析
说明 TLS(Transport Layer Security Protocol),即安全传输层协议,其核心是加密两台计算机之间的通信.libvirt中使用TLS,可以实现libvirt的安全加密.可 ...
- 《VR入门系列教程》之21---使用Unity开发GearVR应用
使用Unity开发GearVR应用 上一章我们介绍了如何运用Unity3D开发Oculus Rift应用,当然,这个便宜且强大的游戏引擎也可以用于GearVR的应用开发,这时我们需要用到Ocu ...
- File文件类
目录 File文件类 File类的构造方法 File类的创建功能 File类的重命名 File类的删除功能 File类的判断功能 File类的获取功能 文件名称过滤器 File文件类 File:文件和 ...
- PhpCms V9中的{date('Y-m-d',$r[inputtime])}问题解决方法
不少朋友会碰到这个问题:在PhpCms V9中的首页或者文章内容页调用发布时间{date('Y-m-d',$r[inputtime])}调用显示1970-01-01,然后尝试用截断的方法也没有成功,应 ...
- ACM线性基学习笔记
https://www.cnblogs.com/31415926535x/p/11260897.html 概述 最近的几场多校出现了好几次线性基的题目,,会想起之前在尝试西安区域赛的一道区间异或和最大 ...
- spring 注解验证@NotNull等使用方法
@Null 被注释的元素必须为null@NotNull 被注释的元素不能为null@AssertTrue 被注释的元素必须为true@AssertFalse 被注释的元素必须为false@Min(va ...
- .net持续集成测试篇之Nunit常见断言
系列目录 Nunit测试基础之简单断言 在开始本篇之前需要补充一些内容,通过前面搭建Nunit测试环境我们知道要使一个方法成为单元测试方法首先要在此方法所在类加上TestFixture注解,并且在该方 ...
- javascript+jQuery补充
一.jQuery事件绑定 <div class='c1'> <div> <div class='title'>菜单一</div> <div cla ...
- .net core(c#)拟合圆测试
说明 很多时候,我们需要运动物体的转弯半径去描述其机器性能.但在大多数的现实条件下,我们只能够获取到运动物体的 GPS 位置点集,并不能直接得到转弯半径或者圆心位置.为此,我们可以利用拟合圆的方式得到 ...
- 用mongodb 固定集合实现只保留固定数量的记录,自动淘汰老旧数据
在一个保存report记录的场景中,我们使用MongoDB进行数据存储 example: db: report Collection: daily_report 创建db: use report; ...