Перейти к основному содержимому

Руководство по MapDB

· 8 мин. чтения

1. Введение

В этой статье мы рассмотрим библиотеку MapDB — встроенный движок базы данных, доступ к которому осуществляется через API, похожий на коллекцию.

Мы начнем с изучения основных классов DB и DBMaker , которые помогают настраивать, открывать и управлять нашими базами данных. Затем мы рассмотрим несколько примеров структур данных MapDB, которые хранят и извлекают данные.

Наконец, мы рассмотрим некоторые из режимов в памяти, прежде чем сравнивать MapDB с традиционными базами данных и коллекциями Java.

2. Хранение данных в MapDB

Во-первых, давайте представим два класса, которые мы будем постоянно использовать в этом руководстве — DB и DBMaker. Класс DB представляет открытую базу данных. Его методы вызывают действия для создания и закрытия коллекций хранилища для обработки записей базы данных, а также обработки транзакционных событий.

DBMaker управляет настройкой, созданием и открытием базы данных. В рамках конфигурации мы можем выбрать размещение нашей базы данных либо в памяти, либо в нашей файловой системе.

2.1. Простой пример HashMap

Чтобы понять, как это работает, давайте создадим новую базу данных в памяти.

Во-первых, давайте создадим новую базу данных в памяти, используя класс DBMaker :

DB db = DBMaker.memoryDB().make();

Как только наш объект БД запущен и запущен, мы можем использовать его для создания HTreeMap для работы с записями нашей базы данных:

String welcomeMessageKey = "Welcome Message";
String welcomeMessageString = "Hello ForEach!";

HTreeMap myMap = db.hashMap("myMap").createOrOpen();
myMap.put(welcomeMessageKey, welcomeMessageString);

HTreeMap — это реализация HashMap в MapDB . Итак, теперь, когда у нас есть данные в нашей базе данных, мы можем получить их с помощью метода get :

String welcomeMessageFromDB = (String) myMap.get(welcomeMessageKey);
assertEquals(welcomeMessageString, welcomeMessageFromDB);

Наконец, теперь, когда мы закончили с базой данных, мы должны закрыть ее, чтобы избежать дальнейших изменений:

db.close();

Чтобы хранить наши данные в файле, а не в памяти, все, что нам нужно сделать, это изменить способ создания экземпляра нашего объекта БД :

DB db = DBMaker.fileDB("file.db").make();

В нашем примере выше не используются параметры типа. В результате мы вынуждены приводить наши результаты к работе с определенными типами. В нашем следующем примере мы представим сериализаторы , чтобы исключить необходимость приведения типов.

2.2. Коллекции

MapDB включает в себя различные типы коллекций. Чтобы продемонстрировать, давайте добавим и получим некоторые данные из нашей базы данных, используя NavigableSet , который работает так, как вы могли бы ожидать от Java Set :

Начнем с простого создания экземпляра нашего объекта БД :

DB db = DBMaker.memoryDB().make();

Далее создадим наш NavigableSet :

NavigableSet<String> set = db
.treeSet("mySet")
.serializer(Serializer.STRING)
.createOrOpen();

Здесь сериализатор обеспечивает сериализацию и десериализацию входных данных из нашей базы данных с использованием объектов String .

Далее добавим некоторые данные:

set.add("ForEach");
set.add("is awesome");

Теперь давайте проверим, что наши два различных значения были правильно добавлены в базу данных:

assertEquals(2, set.size());

Наконец, поскольку это набор, давайте добавим повторяющуюся строку и убедимся, что наша база данных по-прежнему содержит только два значения:

set.add("ForEach");

assertEquals(2, set.size());

2.3. Транзакции

Подобно традиционным базам данных, класс DB предоставляет методы для фиксации и отката данных, которые мы добавляем в нашу базу данных.

Чтобы включить эту функцию, нам нужно инициализировать нашу БД с помощью метода transactionEnable :

DB db = DBMaker.memoryDB().transactionEnable().make();

Далее давайте создадим простой набор, добавим некоторые данные и зафиксируем их в базе данных:

NavigableSet<String> set = db
.treeSet("mySet")
.serializer(Serializer.STRING)
.createOrOpen();

set.add("One");
set.add("Two");

db.commit();

assertEquals(2, set.size());

Теперь давайте добавим в нашу базу данных третью незафиксированную строку:

set.add("Three");

assertEquals(3, set.size());

Если нас не устраивают наши данные, мы можем откатить данные, используя метод отката БД :

db.rollback();

assertEquals(2, set.size());

2.4. Сериализаторы

MapDB предлагает большое количество сериализаторов, которые обрабатывают данные в коллекции . Наиболее важным параметром построения является имя, которое идентифицирует отдельную коллекцию в объекте БД :

HTreeMap<String, Long> map = db.hashMap("indentification_name")
.keySerializer(Serializer.STRING)
.valueSerializer(Serializer.LONG)
.create();

Хотя сериализация рекомендуется, она необязательна и может быть пропущена. Однако стоит отметить, что это приведет к замедлению общего процесса сериализации.

3. HTreeMap

HTreeMap от MapDB предоставляет коллекции HashMap и HashSet для работы с нашей базой данных. HTreeMap представляет собой сегментированное хэш-дерево и не использует хэш-таблицу фиксированного размера. Вместо этого он использует автоматически расширяющееся дерево индексов и не пересчитывает все свои данные по мере роста таблицы. В довершение всего, HTreeMap является потокобезопасным и поддерживает параллельную запись с использованием нескольких сегментов.

Для начала давайте создадим простой экземпляр HashMap , который использует String как для ключей, так и для значений:

DB db = DBMaker.memoryDB().make();

HTreeMap<String, String> hTreeMap = db
.hashMap("myTreeMap")
.keySerializer(Serializer.STRING)
.valueSerializer(Serializer.STRING)
.create();

Выше мы определили отдельные сериализаторы для ключа и значения. Теперь, когда наш HashMap создан, давайте добавим данные с помощью метода put :

hTreeMap.put("key1", "value1");
hTreeMap.put("key2", "value2");

assertEquals(2, hTreeMap.size());

Поскольку HashMap работает с методом hashCode объекта , добавление данных с использованием того же ключа приводит к перезаписыванию значения:

hTreeMap.put("key1", "value3");

assertEquals(2, hTreeMap.size());
assertEquals("value3", hTreeMap.get("key1"));

4. Карта сортированной таблицы

SortedTableMap MapDB хранит ключи в таблице фиксированного размера и использует двоичный поиск для поиска. Стоит отметить, что после подготовки карта доступна только для чтения.

Давайте рассмотрим процесс создания и запроса SortedTableMap. Мы начнем с создания тома с отображением памяти для хранения данных, а также приемника для добавления данных. При первом вызове нашего тома мы установим для флага только для чтения значение false , чтобы мы могли записывать в том:

String VOLUME_LOCATION = "sortedTableMapVol.db";

Volume vol = MappedFileVol.FACTORY.makeVolume(VOLUME_LOCATION, false);

SortedTableMap.Sink<Integer, String> sink =
SortedTableMap.create(
vol,
Serializer.INTEGER,
Serializer.STRING)
.createFromSink();

Затем мы добавим наши данные и вызовем метод create в приемнике, чтобы создать нашу карту:

for(int i = 0; i < 100; i++){
sink.put(i, "Value " + Integer.toString(i));
}

sink.create();

Теперь, когда наша карта существует, мы можем определить объем только для чтения и открыть нашу карту с помощью метода open SortedTableMap :

Volume openVol = MappedFileVol.FACTORY.makeVolume(VOLUME_LOCATION, true);

SortedTableMap<Integer, String> sortedTableMap = SortedTableMap
.open(
openVol,
Serializer.INTEGER,
Serializer.STRING);

assertEquals(100, sortedTableMap.size());

4.1. Бинарный поиск

Прежде чем двигаться дальше, давайте более подробно разберемся, как SortedTableMap использует бинарный поиск.

SortedTableMap разбивает хранилище на страницы, каждая из которых содержит несколько узлов, состоящих из ключей и значений. Внутри этих узлов находятся пары ключ-значение, которые мы определяем в нашем коде Java.

SortedTableMap выполняет три бинарных поиска для получения правильного значения:

  1. Ключи для каждой страницы хранятся в куче в виде массива. SortedTableMap выполняет двоичный поиск, чтобы найти нужную страницу.
  2. Далее происходит распаковка для каждого ключа в узле. Двоичный поиск устанавливает правильный узел в соответствии с ключами.
  3. Наконец, SortedTableMap просматривает ключи внутри узла, чтобы найти правильное значение.

5. Режим памяти

MapDB предлагает три типа хранилища в памяти. Давайте кратко рассмотрим каждый режим, поймем, как он работает, и изучим его преимущества.

5.1. В куче

В режиме on-heap объекты хранятся в простой Java Collection Map . Он не использует сериализацию и может быть очень быстрым для небольших наборов данных.

Однако, поскольку данные хранятся в куче, набор данных управляется сборкой мусора (GC). Продолжительность сборки мусора увеличивается с увеличением размера набора данных, что приводит к падению производительности.

Давайте посмотрим на пример, указывающий режим в куче:

DB db = DBMaker.heapDB().make();

5.2. Байт[]

Второй тип хранилища основан на байтовых массивах. В этом режиме данные сериализуются и сохраняются в массивы размером до 1 МБ. Хотя технически этот метод находится в куче, он более эффективен для сборки мусора.

Это рекомендуется по умолчанию и использовалось в нашем примере « Hello ForEach» :

DB db = DBMaker.memoryDB().make();

5.3. DirectByteBuffer

Последнее хранилище основано на DirectByteBuffer. Прямая память, представленная в Java 1.4, позволяет передавать данные непосредственно в собственную память, а не в кучу Java. В результате данные будут храниться полностью вне кучи.

Мы можем вызвать хранилище этого типа с помощью:

DB db = DBMaker.memoryDirectDB().make();

6. Почему MapDB?

Итак, зачем использовать MapDB?

6.1. MapDB против традиционной базы данных

MapDB предлагает широкий набор функций базы данных, сконфигурированных всего несколькими строками кода Java. Когда мы используем MapDB, мы можем избежать часто трудоемкой настройки различных служб и подключений, необходимых для работы нашей программы.

Помимо этого, MapDB позволяет нам получить доступ к сложности базы данных с помощью Java Collection. С MapDB нам не нужен SQL, и мы можем получить доступ к записям с помощью простых вызовов метода get .

6.2. MapDB против простых коллекций Java

Коллекции Java не сохранят данные нашего приложения после того, как оно перестанет выполняться. MapDB предлагает простой, гибкий, подключаемый сервис, который позволяет нам быстро и легко сохранять данные в нашем приложении, сохраняя при этом полезность типов коллекций Java.

7. Заключение

В этой статье мы подробно рассмотрели встроенный движок базы данных MapDB и структуру коллекций.

Мы начали с рассмотрения основных классов DB и DBMaker для настройки, открытия и управления нашей базой данных. Затем мы рассмотрели несколько примеров структур данных, которые MapDB предлагает для работы с нашими записями. Наконец, мы рассмотрели преимущества MapDB по сравнению с традиционной базой данных или коллекцией Java.

Как всегда, код примера доступен на GitHub .