Chronicle Map для Java: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс
Chronicle Map Java: методы, примеры, установка | PythonLib

Chronicle Map для Java: полное руководство с примерами

Chronicle Map — это высокопроизводительная, off-heap (вне кучи) реализация ConcurrentMap для Java, разработанная компанией Chronicle Software. В отличие от стандартных HashMap или ConcurrentHashMap, которые хранят данные в куче JVM (heap), Chronicle Map размещает данные непосредственно в оперативной памяти за пределами управляемой кучи. Это позволяет избежать накладных расходов, связанных с сборкой мусора (GC), и обеспечивает предсказуемую задержку даже при работе с большими объёмами данных (гигабайты и терабайты).

Эта библиотека идеально подходит для сценариев, где требуется низкая задержка, высокая пропускная способность и работа с данными, которые не должны влиять на время пауз GC. Типичные применения: финансовые системы (торговые платформы), кэширование, системы реального времени, хранение сессий, а также любые приложения, где важна детерминированная производительность. Chronicle Map поддерживает многопоточный доступ, сериализацию ключей и значений, а также может быть сконфигурирована для персистентности (сохранения данных на диск).

В этой статье мы подробно рассмотрим установку, основные методы, приведём множество рабочих примеров и дадим советы по производительности. К концу вы сможете уверенно использовать Chronicle Map в своих Java-проектах.

1. Установка

Chronicle Map распространяется через Maven Central. Для использования в вашем проекте добавьте следующую зависимость в файл pom.xml (Maven):

<dependency>
    <groupId>net.openhft</groupId>
    <artifactId>chronicle-map</artifactId>
    <version>3.25ea0</version> <!-- Проверьте актуальную версию на Maven Central -->
</dependency>

Если вы используете Gradle, добавьте строку в build.gradle:

implementation 'net.openhft:chronicle-map:3.25ea0'
Примечание: Версия 3.x является последней стабильной линейкой. Для новых проектов рекомендуется использовать именно её. Проверить актуальную версию можно на Maven Repository.

2. Быстрый старт — минимальный рабочий пример

Создадим простую карту, которая хранит пары "имя пользователя" (String) и "количество входов" (Integer).

import net.openhft.chronicle.map.ChronicleMap;

public class QuickStart {
    public static void main(String[] args) {
        // 1. Конфигурация и создание карты
        ChronicleMap<String, Integer> userLoginCount = ChronicleMap
            .of(String.class, Integer.class)
            .name("user-login-map")
            .averageKey("user")       // Средняя длина ключа для оптимизации
            .averageValue(1)          // Среднее значение для оптимизации
            .entries(1000)            // Ожидаемое количество записей
            .create();

        // 2. Вставка данных
        userLoginCount.put("alice", 5);
        userLoginCount.put("bob", 3);
        userLoginCount.put("charlie", 10);

        // 3. Чтение данных
        System.out.println("Входы alice: " + userLoginCount.get("alice"));
        System.out.println("Входы bob: " + userLoginCount.get("bob"));

        // 4. Итерация (осторожно, может быть медленной)
        userLoginCount.forEach((key, value) -> 
            System.out.println(key + " -> " + value));

        // 5. Закрытие карты (освобождение off-heap памяти)
        userLoginCount.close();
    }
}

Этот пример демонстрирует базовую работу: создание, вставка, чтение и итерация. Обратите внимание на методы averageKey() и averageValue() — они критически важны для эффективного использования памяти.

3. Основные методы и классы

Рассмотрим ключевые методы ChronicleMap. Все они являются частью интерфейса ChronicleMap, который расширяет java.util.concurrent.ConcurrentMap.

3.1. ChronicleMap.of(Class<K>, Class<V>)

Сигнатура: public static <K, V> ChronicleMapBuilder<K, V> of(Class<K> keyClass, Class<V> valueClass)

Описание: Статический фабричный метод, который возвращает билдер для настройки карты. Это единственный способ создать экземпляр ChronicleMap. Билдер позволяет задать все параметры: размер, средние значения, сериализаторы и т.д.

Пример:

ChronicleMapBuilder<Long, String> builder = ChronicleMap.of(Long.class, String.class);
ChronicleMap<Long, String> map = builder
    .name("my-map")
    .averageValue("some-average-string")
    .entries(10_000)
    .create();

3.2. put(K key, V value)

Сигнатура: V put(K key, V value)

Описание: Вставляет или обновляет запись. Возвращает предыдущее значение, связанное с ключом, или null, если ключа не было. Работает атомарно.

Пример:

ChronicleMap<String, String> map = ...;
String old = map.put("key1", "value1"); // null
String old2 = map.put("key1", "value2"); // "value1"

3.3. get(Object key)

Сигнатура: V get(Object key)

Описание: Возвращает значение, связанное с ключом, или null, если ключ отсутствует. Важно: возвращаемое значение — это копия данных из off-heap памяти, а не ссылка на внутреннее хранилище.

Пример:

ChronicleMap<Integer, Double> map = ...;
map.put(1, 3.14);
Double pi = map.get(1); // 3.14
Double missing = map.get(2); // null

3.4. remove(Object key)

Сигнатура: V remove(Object key)

Описание: Удаляет запись по ключу. Возвращает удалённое значение или null, если ключ не найден. Операция атомарна.

Пример:

ChronicleMap<String, Integer> map = ...;
map.put("temp", 42);
Integer removed = map.remove("temp"); // 42
Integer removedAgain = map.remove("temp"); // null

3.5. containsKey(Object key)

Сигнатура: boolean containsKey(Object key)

Описание: Проверяет, существует ли запись с указанным ключом. Возвращает true или false. Этот метод может быть быстрее, чем get(), если значение не нужно.

Пример:

ChronicleMap<UUID, String> map = ...;
UUID id = UUID.randomUUID();
map.put(id, "data");
if (map.containsKey(id)) {
    System.out.println("Ключ найден!");
}

3.6. putIfAbsent(K key, V value)

Сигнатура: V putIfAbsent(K key, V value)

Описание: Атомарно вставляет значение, только если ключ отсутствует. Возвращает существующее значение, если ключ уже есть, или null в случае успешной вставки.

Пример:

ChronicleMap<String, String> cache = ...;
String result = cache.putIfAbsent("config", "default");
if (result == null) {
    System.out.println("Значение установлено");
} else {
    System.out.println("Уже было: " + result);
}

3.7. replace(K key, V oldValue, V newValue)

Сигнатура: boolean replace(K key, V oldValue, V newValue)

Описание: Атомарно заменяет значение, только если текущее значение равно oldValue. Используется для оптимистичных блокировок (CAS-операции).

Пример:

ChronicleMap<String, Integer> counter = ...;
counter.put("visits", 10);
boolean replaced = counter.replace("visits", 10, 11); // true
boolean failed = counter.replace("visits", 10, 12); // false, т.к. сейчас 11

3.8. forEach(BiConsumer<? super K, ? super V> action)

Сигнатура: void forEach(BiConsumer<? super K, ? super V> action)

Описание: Выполняет заданное действие для каждой записи в карте. Важно: итерация по ChronicleMap может быть медленной, так как требует десериализации каждого ключа и значения. Не используйте для больших карт в критических к производительности участках.

Пример:

ChronicleMap<String, Double> prices = ...;
prices.put("apple", 1.2);
prices.put("banana", 0.8);
prices.forEach((key, value) -> 
    System.out.println("Товар: " + key + ", цена: " + value));

3.9. close()

Сигнатура: void close()

Описание: Закрывает карту и освобождает всю связанную с ней off-heap память. После вызова close() карта становится непригодной для использования. Рекомендуется вызывать в блоке finally или использовать try-with-resources.

Пример:

try (ChronicleMap<Integer, String> map = ChronicleMap
        .of(Integer.class, String.class)
        .name("temp-map")
        .entries(100)
        .create()) {
    map.put(1, "one");
    // Автоматически закроется при выходе из блока try
}

3.10. size()

Сигнатура: int size()

Описание: Возвращает количество записей в карте. В отличие от ConcurrentHashMap, этот метод не блокирует карту, но может возвращать неточное значение при одновременных модификациях.

Пример:

ChronicleMap<String, String> map = ...;
map.put("a", "1");
map.put("b", "2");
System.out.println("Размер: " + map.size()); // 2

4. Реальный практический пример: Кэш сессий пользователей

Представим, что мы разрабатываем высоконагруженный веб-сервер, которому необходимо хранить сессии пользователей. Сессия содержит ID пользователя, время последней активности и некоторые метаданные. Использование Chronicle Map позволит нам хранить миллионы сессий без нагрузки на GC.

import net.openhft.chronicle.map.ChronicleMap;
import java.io.Serializable;
import java.time.Instant;
import java.util.UUID;

// Класс, представляющий данные сессии
class UserSession implements Serializable {
    private static final long serialVersionUID = 1L;
    final UUID userId;
    final String userName;
    volatile Instant lastAccess;
    final String metadata;

    public UserSession(UUID userId, String userName, String metadata) {
        this.userId = userId;
        this                    

Рекомендаций