zoukankan      html  css  js  c++  java
  • 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("-----------------------------------------
    ");
            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自动创建的,切记不要多此一举,把文件也创建了,那样会报错的。

  • 相关阅读:
    微软WP7本地数据库之Sqlite编程技巧(转)
    AutoResetEvent详解
    桥接模式的简单分析
    解决VS2008 调试启动特别慢
    软件概要设计
    解决windows8不能安装ZUNE的问题
    CDATA的对特殊字符作用说明
    DataTable对象在内存中的使用(二)
    DataTable对象在内存中的使用(一)
    关于MVC3 CODE FIRST的安装
  • 原文地址:https://www.cnblogs.com/shuimutong/p/11438216.html
Copyright © 2011-2022 走看看