惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Security Blog
Microsoft Security Blog
J
Java Code Geeks
GbyAI
GbyAI
aimingoo的专栏
aimingoo的专栏
L
LangChain Blog
I
InfoQ
D
Docker
F
Fortinet All Blogs
Y
Y Combinator Blog
Martin Fowler
Martin Fowler
月光博客
月光博客
B
Blog
Engineering at Meta
Engineering at Meta
T
Tailwind CSS Blog
罗磊的独立博客
博客园_首页
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
D
DataBreaches.Net
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
B
Blog RSS Feed
IT之家
IT之家
V
V2EX

热爱生活与梦想

迁移LeanCloud的Waline评论数据到TiDB/Neon 25年江浙地区暑假自驾游之感慨 使用阿里云灵码继续完成构建 SSL 证书管理器 亲手给儿子做的棒棒糖摩天轮六一礼物 使用腾讯云 CodeBuddy 构建 SSL 证书管理器 修复代码块默认高度相关缺陷 HugoNexT4.7.2 新功能和升级提示 改善代码块折叠和选中功能 支持Github风格的警告样式 新增音乐短代码功能支持 给文章添加摘要和过期提示功能 回顾24年的过去总结 修复Mathjax行内显示公式的问题 给Hugo文章增加阅读更多跳转的锚点定位功能 Win10隐匿的VPN设置和L2TP连接常见错误 Go语言中“糟糕”的日期时间格式化设计 DaoCloud道客提供的免费Docker镜像代理服务 感谢万能淘宝让自己吃到喜欢的美食 WSL运行时遇到未知异常错误无法使用 隐藏的换行符导致Base64加密解密失败 MySQL自带客户端直接免密登录操作 Linux系统中删除目录软链接的注意项 Java程序调用外网API时CA问题 如何不关机重启WSL2恢复虚拟服务 在Windows上安装Podman容器平台做虚拟化 记一次无法弹出移动硬盘的记录 为友情链接添加自动检测脚本 重新激活Github的2FA认证 Linux中使用tar压缩命令排除文件 继续小米手环的健康生活之旅
MapDB 同步读写示例
凡梦星尘 · 2016-01-20 · via 热爱生活与梦想

MapDB 是一个快速、易用的嵌入式Java数据库引擎. 最主要的特点之一就是支持磁盘存储,直接把内存中的Hash Map同步写入到磁盘. 另外特别惊喜的是它支持ACID事务,MVCC隔离, 且有全职的开发者支持.

看完官方的文档与示例后,基本上可以确定它符合业务场景的使用要求.另外发现官方正在重构3.x的版本, 但应该不会这么快发布吧.用google搜索了下关于MapDB的使用案例, 也不是很多. 可能是本来官方的文档就齐全有关吧,API也不复杂,跟着官方的文档走一遍就可以上手了.

动手测试了简单的示例后, 突然冒出一个疑问, 如何实现同时操作磁盘上的一个数据库, 以及同一个HashMap呢? 这里需要明白的, MapDB存储到磁盘上的数据库文件,并非只是存放了一个HashMap, 这有点类似数据库里可以有多张表的概念相同. 那么数据库是可以支持多连接的, MapDB是否也同样支持呢?(理想确实很丰满,但现实太骨感了!)

初步检验的结果是, MapDB并不支持同时访问磁盘上的同一文件. 那么也就是只能创建一个长连接, 直到业务功能处理完成再关闭它. 幸运的是它支持对已经存在或是运行中的同一个HashMap进行读写操作. 下面来看看简单的示例代码:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

import org.mapdb.BTreeMap;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import java.io.File;
import java.util.Map;
import java.util.Random;
import java.util.SortedMap;

import static org.testng.Assert.*;

/**
 * MapDB 测试
 *
 * @author 凡梦星尘(elkan1788@gmail.com)
 * @since 2016.1.19
 */
public class MapDBTest {

    private DB diskDB;

    Map<Integer, String> data;

    @BeforeTest
    public void init() {
        // 文件名字可以自己定义
        File dbFile = new File("D:/mapdb.data");
        // DB有且只打开一次连接
        diskDB = DBMaker.fileDB(dbFile)
                // 很是好奇,关闭锁定,还是不支持多事务访问同一个数据库文件
                .fileLockDisable()
                // 最好开启,在程度异常或JVM关闭可正常关闭数据库
                // 有过一次无法访问未关闭数据库文件的异常
                .closeOnJvmShutdown()
                // 如果不需要回滚的可以关闭,提高读写效率
                .transactionDisable()
                // 这里测试所没不保留磁盘文件
                .deleteFilesAfterClose()
                // 这里没有找到读取的API,或者就是不支持多连接的吧
                .make();
    }

    @AfterTest
    public void destroy() {
        assertTrue(!data.isEmpty());
        // 本应该是99才对,但会合并内存中其它数据
        Map<Integer, String> temp = diskDB.treeMap("sort_mapdb");
        assertEquals(temp.size(), 100);

        // 这里需要注意下,有可能make成功的数据库也是关闭的
        // 如果不做检查的话,可能抛出:IllegalAccessError("DB was already closed")
        if (diskDB.isClosed()) {
            diskDB.isClosed();
        }
    }

    @Test(invocationCount = 10)
    public void testSyncWrite() throws Exception {
        // 支持多种类型Map,如B+ tree, sort等等
        // 但value貌似支持引用类型, 不支持Object, 可能是
        // 跟序列化到磁盘存储有关
        data = diskDB.treeMapCreate("nice_mapdb")
                // 开启快速计数器
                .counterEnable()
                // 这步很关键,如果不带get,那么就只是make,无法支持多连接
                .makeOrGet();
        int len = 99;
        int ran = new Random().nextInt(100)+1;
        while (--len >= 0) {
            data.put(len * ran, "value-"+len * ran);
        }

        assertFalse(data.isEmpty());
    }

    @Test(invocationCount = 10)
    public void testReadAndDel() throws Exception {
        data = diskDB.treeMapCreate("nice_mapdb")
                .counterEnable()
                .makeOrGet();
        if (!data.isEmpty()) {
            for (Integer key : data.keySet()) {
                if (key % 2 == 0 || key % 5 == 0) {
                    data.remove(key);
                }
            }

            assertTrue(data.size() > 0);
        }
    }

    @Test
    public void testOtherMap() throws Exception {
        SortedMap<Integer, String> data = diskDB.treeMapCreate("sort_mapdb")
                                        .counterEnable()
                                        .makeOrGet();

        int len = 99;
        while (--len >= 0) {
            data.put(len, "sorted-"+len);
        }

        assertNotNull(data);

        // 创建另一个map
        BTreeMap<Integer, String> btree = diskDB.treeMapCreate("sort_mapdb2")
                                        .counterEnable()
                                        .makeOrGet();

        // 很是奇怪, 为何这里的name没有效果, 会自动合并到同时一时内存所有treeMap中
        SortedMap<Integer, String> tree = diskDB.treeMap("sort_mapdb1");
        tree.put(100, "sorted-100");

        btree.put(100, "sorted-101");

        assertEquals(tree.get(100), "sorted-100");
        assertEquals(data.get(100), "sorted-100");
    }

}

在这里没有详细的探讨关于MapDB是如何实现与磁盘持久化同步, 直接使用官方默认的值, 当然你也可以自己配置读写同步的心跳时间间隔. 在测试的过程观察发现, MapDB在创建磁盘的数据库文件时, 初始化大小写为2MB, 然后在同步内存数据时, 会先产生出一个临时文件, 当这个临时文件达到一定大小时就会合并到主体数据库文件. 至于其它的功能和代码中的疑问, 有待继续观察, 欢迎共同交流.

参考资料: