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.
|