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

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

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

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


person junkie    schedule 06.02.2018    source источник


Ответы (2)


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

Если вы хотите читать/записывать только отдельные элементы в больших списках, вам следует использовать 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 javadoc осторожно.
  2. Data.bytes() формально возвращает RandomDataInput, но вы можете быть уверены (это указано в Data.bytes() javadoc), что на самом деле это экземпляр BytesStore (это комбинация RandomDataInput и RandomDataOutput).
  3. Предполагая, что предоставлены правильные SizedReader и SizedWriter (или DataAccess). Обратите внимание, что используется метод «размер соединения байтов/элемент», такой же, как в примере, приведенном в SizedReader и SizedWriter раздел документа, PointListSizeMarshaller. Вы можете основывать свой LongListMarshaller на этом примере класса.
  4. Это указание указано, см. ChecksumEntry javadoc и раздел о контрольных суммах в документе. Если у вас есть только хранящаяся в памяти (не сохраняемая) карта хроники или отключены контрольные суммы, этот вызов можно опустить.

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

person leventov    schedule 06.02.2018
comment
Большое спасибо @leventov за отличный и подробный ответ. У меня все работает как объяснил. - person junkie; 08.02.2018
comment
Несколько вопросов: 1) Я реализовал SizedReader+Writer. Нужен ли мне DataAccess или SizedWriter достаточно быстр для примитивных массивов? Я просмотрел ByteArrayDataAccess, но не ясно, как его портировать для длинных массивов, учитывая, что внутреннее хранилище HeapBytesStore настолько специфично для byte[]/ByteBuffers? 2) Является ли блокировка чтения/записи посредником при чтении и записи нескольких процессов на одном компьютере или только в рамках одного процесса? 3) При хранении объектов с заранее неизвестным переменным размером, как значения, которые вызовут фрагментацию из кучи и в сохраняемом файле? - person junkie; 08.02.2018

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

Я реализовал 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 и контрольный список пользовательской сериализации в учебник с большим вниманием тоже.


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

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


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

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

person leventov    schedule 08.02.2018
comment
Извините за задержку. Еще раз большое спасибо @leventov за то, что так подробно ответили на все вопросы. Я посмотрю дальше документы в местах, которые вы предложили, чтобы лучше понять. - person junkie; 10.02.2018