背景

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使用入门的更多相关文章

  1. Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求

    上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...

  2. ABP入门系列(1)——学习Abp框架之实操演练

    作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...

  3. Oracle分析函数入门

    一.Oracle分析函数入门 分析函数是什么?分析函数是Oracle专门用于解决复杂报表统计需求的功能强大的函数,它可以在数据中进行分组然后计算基于组的某种统计值,并且每一组的每一行都可以返回一个统计 ...

  4. Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数

    上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...

  5. Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数

    上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...

  6. Angular2入门系列教程4-服务

    上一篇文章 Angular2入门系列教程-多个组件,主从关系 在编程中,我们通常会将数据提供单独分离出来,以免在编写程序的过程中反复复制粘贴数据请求的代码 Angular2中提供了依赖注入的概念,使得 ...

  7. wepack+sass+vue 入门教程(三)

    十一.安装sass文件转换为css需要的相关依赖包 npm install --save-dev sass-loader style-loader css-loader loader的作用是辅助web ...

  8. wepack+sass+vue 入门教程(二)

    六.新建webpack配置文件 webpack.config.js 文件整体框架内容如下,后续会详细说明每个配置项的配置 webpack.config.js直接放在项目demo目录下 module.e ...

  9. wepack+sass+vue 入门教程(一)

    一.安装node.js node.js是基础,必须先安装.而且最新版的node.js,已经集成了npm. 下载地址 node安装,一路按默认即可. 二.全局安装webpack npm install ...

随机推荐

  1. 聊聊HTML5中的Web Notification桌面通知

    有的时候我们会在桌面右下角看到这样的提示: 这种桌面提示是HTML5新增的 Web Push Notifications 技术. Web Notifications 技术使页面可以发出通知,通知将被显 ...

  2. 使用钉钉对接禅道的bug系统,实现禅道提的bug实时在钉钉提醒并艾特对应的开发人员处理

    现在公司测试中有一个痛点是每次测试人员提完bug后,需要定期去提醒开发人员查看禅道的bug记录及修复bug. 导致测试人员在项目测试中不仅要测试整个软件,还要负起实时监督提醒功能的“保姆角色”,身心疲 ...

  3. Java的自动装箱/拆箱

    概述 自JDK1.5开始, 引入了自动装箱/拆箱这一语法糖, 它使程序员的代码变得更加简洁, 不再需要进行显式转换.基本类型与包装类型在某些操作符的作用下, 包装类型调用valueOf()方法将原始类 ...

  4. 有容云-PPT | 当微服务遇见容器

    编者注: 本文为10月29日有容云高级技术顾问龙淼在Docker Live时代线下系列-广州站中演讲的PPT,本次线下沙龙为有容云倾力打造Docker Live时代系列主题线下沙龙,每月一期畅聊容器技 ...

  5. spring学习笔记之---bean属性注入

    bean属性注入 (一)构造方法的属性注入 1.Student.java package entity; public class Student { private String name; pri ...

  6. WinForm开发中通用附件管理控件设计开发参考

    1.引言 在WinForm开发中,文件附件的管理几乎在任何一个应用上都会存在,是一个非常通用集中的公共模块.我们日常记录会伴随着有图片.文档等附件形式来展现,如果为每个业务对象都做一个附件管理,或者每 ...

  7. 在 树莓派(Raspberry PI) 中使用 Docker 运行 aspnetcore/dotnetcore 应用

    本文主要利用 Microsoft 提供的 Dockerfile 进行安装. 虽然Raspberry PI 3 CPU支持 armv8 指令集 ,但是在 docker info 还是识别为 " ...

  8. 消息中间件-activemq实战之消息持久化(六)

    对于activemq消息的持久化我们在第二节的时候就简单介绍过,今天我们详细的来分析一下activemq的持久化过程以及持久化插件.在生产环境中为确保消息的可靠性,我们肯定的面临持久化消息的问题,今天 ...

  9. X-Admin&ABP框架开发-设置管理

    在网站开发中,设置是不可缺少的一环,如用户设置.系统设置.甚至是租户设置等.ABP对于设置的管理已经做了很好的处理,我们可以借助巨人的力量来完成我们的冒险. ABP官网地址:https://aspne ...

  10. Java.基础 -------- 一个Java源文件中可以有很多类,但只能有一个类是public的

    一个Java源文件中可以有很多类,但只能有一个类是public的        Java程序是从一个public类main函数开始执行的,只能有一个public是为了给类装载器提供方便,一个publi ...