This pair of interfaces is configured using ChronicleMapBuilder.keyReaderAndDataAccess() or
valueReaderAndDataAccess() for the key, or value type, of the map respectively.
The reader part, SizedReader, is the same as in SizedWriter and SizedReader pair. DataAccess is an "advanced" interface to replace SizedWriter.
The main method in DataAccess is Data<T> getData(@NotNull T instance). It returns a Data
accessor which is used to write a "serialized" form of the instance to off-heap memory. Data.size()
on the returned Data object is used for the same purpose as the SizedWriter.size() method in
the SizedWriter and SizedReader pair interfaces. Data.writeTo()
is used instead of SizedWriter.write().
DataAccess assumes that the Data object, returned from the getData() method is cached in some way. That is why it also has an uninit() method to clear references to the serialized object after a query operation to a Chronicle Map is complete to prevent memory leaks. This, in turn, implies that the DataAccess implementation is stateful. Therefore, DataAccess is made a sub-interface of
StatefulCopyable to force all DataAccess implementations to implement StatefulCopyable as well.
See Understanding StatefulCopyable for more information
on this.
If your DataAccess implementation is not actually stateful, it is free to return this from the StatefulCopyable.copy() method.
The DataAccess interface is primarily intended for "serializing" objects that are already sequences of bytes, and in fact do not require serialization; for example, byte[], ByteBuffer, arrays of Java
primitives. For such types of objects, DataAccess allows bypassing of the intermediate buffering, copying data directly from objects to Chronicle Map’s off-heap memory.
For example, look at the DataAccess implementation for byte[]:
public class ByteArrayDataAccess extends AbstractData<byte[]> implements DataAccess<byte[]> {
/**
* Cache field
*/
private transient BytesStore<?, ?> bs;
/**
* State field
*/
private transient byte[] array;
public ByteArrayDataAccess() {
initTransients();
}
private void initTransients() {
bs = null;
}
@Override
public RandomDataInput bytes() {
return bs;
}
@Override
public long offset() {
return bs.start();
}
@Override
public long size() {
return bs.capacity();
}
@Override
public byte[] get() {
return array;
}
@Override
public byte[] getUsing(@Nullable byte[] using) {
if (using == null || using.length != array.length)
using = new byte[array.length];
System.arraycopy(array, 0, using, 0, array.length);
return using;
}
@Override
public Data<byte[]> getData(@NotNull byte[] instance) {
array = instance;
bs = BytesStore.wrap(array);
return this;
}
@Override
public void uninit() {
array = null;
bs = null;
}
@Override
public DataAccess<byte[]> copy() {
return new ByteArrayDataAccess();
}
@Override
public void writeMarshallable(@NotNull WireOut wireOut) {
// no fields to write
}
@Override
public void readMarshallable(@NotNull WireIn wireIn) {
// no fields to read
initTransients();
}
@Override
public String toString() {
return new String(array, StandardCharsets.UTF_8);
}
}The getData() method returns this, and the DataAccess implementation implements the Data interface as well. This is recommended practice, because it reduces the number of objects involved (hence pointer chasing), and keeps DataAccess, and Data logic together.
The Data interface puts constraints on equals(), hashCode(), and toString() implementations. This is why ByteArrayDataAccess sub-classes AbstractData, and inherits proper implementations from it.
A serializer strategy implementation can have equals(), hashCode(), and toString() from a very different domain, because those methods are never called on serializers inside Chronicle Map.
The easiest way to implement equals(), hashCode(), and toString() is to extend the AbstractData class. If it is not possible (perhaps the Data implementation already extends some other class), do this
by delegating to dataEquals(), dataHashCode(), and dataToString() default methods, provided in the Data interface.
Corresponding SizedReader for byte[]:
public final class ByteArraySizedReader
implements SizedReader<byte[]>, EnumMarshallable<ByteArraySizedReader> {
public static final ByteArraySizedReader INSTANCE = new ByteArraySizedReader();
private ByteArraySizedReader() {
}
@NotNull
@Override
public byte[] read(@NotNull Bytes in, long size, @Nullable byte[] using) {
if (size < 0L || size > (long) Integer.MAX_VALUE) {
throw new IORuntimeException("byte[] size should be non-negative int, " +
size + " given. Memory corruption?");
}
int arrayLength = (int) size;
if (using == null || arrayLength != using.length)
using = new byte[arrayLength];
in.read(using);
return using;
}
@NotNull
@Override
public ByteArraySizedReader readResolve() {
return INSTANCE;
}
}|
Note
|
If you configure byte[] key, or value type, then this pair of serializers is used as the default.
|