Как сериализовать / десериализовать значение long[] с помощью get/set для случайных индексов с помощью Chronicle Map?

Я новичок в летописи-карте. Я пытаюсь смоделировать карту вне кучи с использованием карты хроники, где ключ является примитивным коротким, а значение - примитивным длинным массивом. Максимальный размер значения длинного массива известен для данной карты. Однако у меня будет несколько карт такого типа, каждая из которых может иметь разный максимальный размер для значения длинного массива. Мой вопрос касается сериализации / десериализации ключа и значения.

Из прочтения документации я понимаю, что для ключа я могу использовать тип значения ShortValue и повторно использовать экземпляр реализации этого интерфейса. Что касается значения, я обнаружил, что на странице говорится о DataAccess и SizedReader, который дает пример для byte[], но я не уверен, как адаптировать это к long[]. Одно дополнительное требование, которое у меня есть, заключается в том, что мне нужно получать и устанавливать значения по произвольным индексам в длинном массиве, не оплачивая стоимость полной сериализации / десериализации всего значения каждый раз.

Итак, мой вопрос: как я могу смоделировать тип значения при построении карты и какой код сериализации / десериализации мне нужен для массива long [], если максимальный размер известен для карты, и мне нужно иметь возможность читать и писать случайным образом индексы без сериализации / десериализации всего значения полезной нагрузки каждый раз? В идеале long [] должен быть закодирован / декодирован непосредственно в / из кучи без выполнения промежуточного преобразования в куче в байт [], а также код карты хроники не будет выделяться во время выполнения. Спасибо.

2 ответа

Решение

Во-первых, я рекомендую использовать какой-то LongList абстракция интерфейса вместо long[], это облегчит борьбу с изменчивостью размера, предоставит альтернативные реализации типа flyweight и т. д.

Если вы хотите читать / писать только отдельные элементы в больших списках, вы должны использовать API расширенного контекста:

/** This method is entirely garbage-free, deserialization-free, and thread-safe. */
void putOneValue(ChronicleMap<ShortValue, LongList> map, ShortValue key, int index,
        long element) {
    if (index < 0) throw throw new IndexOutOfBoundsException(...);
    try (ExternalMapQueryContext<ShortValue, LongList, ?> c = map.getContext(key)) {
        c.writeLock().lock(); // (1)
        MapEntry<ShortValue, LongList> entry = c.entry();
        if (entry != null) {
            Data<LongList> value = entry.value();
            BytesStore valueBytes = (BytesStore) value.bytes(); // (2)
            long valueBytesOffset = value.offset();
            long valueBytesSize = value.size();
            int valueListSize = (int) (valueBytesSize / Long.BYTES); // (3)
            if (index >= valueListSize) throw new IndexOutOfBoundsException(...);
            valueBytes.writeLong(valueBytesOffset + ((long) index) * Long.BYTES,
                element);
            ((ChecksumEntry) entry).updateChecksum(); // (4)
        } else {
            // there is no entry for the given key
            throw ...
        }
    }
}

Заметки:

  1. Вы должны приобрести writeLock() с самого начала, потому что в противном случае readLock() будет получен автоматически при вызове context.entry() метод, и вы не сможете обновить блокировку чтения до блокировки записи позже. Пожалуйста, прочитайте HashQueryContext осторожно
  2. Data.bytes() формально возвращается RandomDataInput, но вы можете быть уверены (это указано в Data.bytes() Javadoc) что это на самом деле экземпляр BytesStore (это комбинация RandomDataInput а также RandomDataOutput).
  3. При условии надлежащего SizedReader а также SizedWriter (или же DataAccess) предоставлены. Обратите внимание, что используется метод "размер соединения байтов / элементов", такой же, как в примере, приведенном в SizedReader а также SizedWriter раздел документации, PointListSizeMarshaller, Вы могли бы основать свой LongListMarshaller на этом примере класса.
  4. Этот актерский состав указан, см. ChecksumEntry Javadoc и раздел о контрольных суммах в док. Если у вас есть только хронологическая карта в памяти (не сохранена) или вы отключили контрольные суммы, этот вызов может быть пропущен.

Реализация одноэлементного чтения аналогична.

Отвечая на дополнительные вопросы:

Я реализовал SizedReader+Writer. Нужен ли мне DataAccess или SizedWriter достаточно быстр для примитивных массивов? Я посмотрел на ByteArrayDataAccess, но не ясно, как портировать его для длинных массивов, учитывая, что внутренний HeapBytesStore настолько специфичен для byte[]/ByteBuffers?

Использование DataAccess вместо SizedWriter позволяет сделать копию данных на одну менее ценную Map.put(key, value), Однако, если в вашем случае использования putOneValue() (как в примере выше) является доминирующим типом запроса, он не будет иметь большого значения. Если Map.put(key, value) (а также replace() и т. д., т. е. важны любые операции "записи полного значения"), все еще возможно реализовать DataAccess за LongList, Это будет выглядеть так:

class LongListDataAccess implements DataAccess<LongList>, Data<LongList>,
        StatefulCopyable<LongListDataAccess> {
    transient ByteStore cachedBytes;
    transient boolean cachedBytesInitialized;
    transient LongList list;

    @Override public Data<LongList> getData(LongList list) {
        this.list = list;
        this.cachedBytesInitialized = false;
        return this;
    }

    @Override public long size() {
        return ((long) list.size()) * Long.BYTES;
    }

    @Override public void writeTo(RandomDataOutput target, long targetOffset) {
        for (int i = 0; i < list.size(); i++) {
            target.writeLong(targetOffset + ((long) i) * Long.BYTES), list.get(i));
        }
    }

    ...
}

Для эффективности методы size() а также writeTo() являются ключевыми. Но важно также правильно реализовать все другие методы (которые я здесь не писал). Читать DataAccess, Data а также StatefulCopyable Javadocs очень тщательно, а также Понимание StatefulCopyable, DataAccess а также SizedReader и пользовательский контрольный список сериализации в руководстве также с большим вниманием.


Блокирует ли чтение / запись посредство нескольких процессов чтения и записи на одном компьютере или только в одном процессе?

Это безопасно для всех процессов, обратите внимание, что интерфейс называется InterProcess ReadWriteUpdateLock.


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

Хранение значения для ключа один раз и без изменения размера значения (и без удаления ключей) после этого не вызовет внешней фрагментации. Изменение размера значения или удаление ключей может привести к внешней фрагментации. ChronicleMapBuilder.actualChunkSize() Конфигурация позволяет торговать между внешней и внутренней фрагментацией. Чем больше кусок, тем меньше внешняя фрагментация, но больше внутренняя фрагментация. Если ваши значения значительно превышают размер страницы (4 КБ), вы можете установить абсурдно большой размер чанка и при этом иметь внутреннюю фрагментацию, связанную с размером страницы, потому что Chronicle Map может использовать функцию ленивого выделения страниц в Linux.

Другие вопросы по тегам