Files
Chronicle-Map/docs/CM_Tutorial_DataAccess.adoc
2020-11-10 15:02:44 +00:00

163 lines
5.9 KiB
Plaintext

= `DataAccess` and `SizedReader`
Neil Clifford
:toc: macro
:toclevels: 1
:css-signature: demo
:toc-placement: macro
:icons: font
toc::[]
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 <<CM_Tutorial_Sized.adoc#,`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 <<CM_Tutorial_Sized.adoc#,`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 <<CM_Tutorial_Understanding.adoc#, Understanding `StatefulCopyable`>> for more infromation
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[]`:
```java
public final class ByteArrayDataAccess extends AbstractData<byte[]> implements DataAccess<byte[]> {
/** Cache field */
private transient HeapBytesStore<byte[]> bs;
/** State field */
private transient byte[] array;
public ByteArrayDataAccess() {
initTransients();
}
private void initTransients() {
bs = HeapBytesStore.uninitialized();
}
@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.init(instance);
return this;
}
@Override
public void uninit() {
array = null;
bs.uninit();
}
@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();
}
}
```
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-class'es `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[]`:
```java
public final class ByteArraySizedReader
implements SizedReader<byte[]>, Marshallable, ReadResolvable<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;
}
@Override
public void writeMarshallable(@NotNull WireOut wireOut) {
// no fields to write
}
@Override
public void readMarshallable(@NotNull WireIn wireIn) {
// no fields to read
}
@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.
'''
<<CM_Tutorial.adoc#,Back to Tutorial>>