Как сериализовать / десериализовать значение 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 ...
}
}
}
Заметки:
- Вы должны приобрести
writeLock()
с самого начала, потому что в противном случае readLock() будет получен автоматически при вызовеcontext.entry()
метод, и вы не сможете обновить блокировку чтения до блокировки записи позже. Пожалуйста, прочитайтеHashQueryContext
осторожно Data.bytes()
формально возвращаетсяRandomDataInput
, но вы можете быть уверены (это указано вData.bytes()
Javadoc) что это на самом деле экземплярBytesStore
(это комбинацияRandomDataInput
а такжеRandomDataOutput
).- При условии надлежащего
SizedReader
а такжеSizedWriter
(или жеDataAccess
) предоставлены. Обратите внимание, что используется метод "размер соединения байтов / элементов", такой же, как в примере, приведенном вSizedReader
а такжеSizedWriter
раздел документации,PointListSizeMarshaller
, Вы могли бы основать свойLongListMarshaller
на этом примере класса. - Этот актерский состав указан, см.
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.