mirror of
https://github.com/OpenHFT/Chronicle-Map.git
synced 2021-09-13 01:26:12 +00:00
Initial re-work of documentation
This commit is contained in:
Executable
+162
@@ -0,0 +1,162 @@
|
||||
= `DataAccess` and `SizedReader`
|
||||
Neil Clifford
|
||||
:toc: macro
|
||||
:toclevels: 1
|
||||
:css-signature: demo
|
||||
:toc-placement: macro
|
||||
:icons: font
|
||||
|
||||
toc::[]
|
||||
|
||||
This pair of interfaces is configured via `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`,`SizedWriter` and `SizedReader`>> pair, so `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 "serialized" form of the instance to off-heap memory. `Data.size()`
|
||||
on the returned `Data` object is used for the same purpose as `SizedWriter.size()` method in
|
||||
<<CM_Tutorial_Sized.adoc#`SizedWriter` and `SizedReader`,`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 `uninit()` method to clear references to the serialized object after query operation to a Chronicle Map is complete to prevent memory leaks. This, in turn, implies that `DataAccess` implementation is stateful. Therefore, `DataAccess` is made a subinterface of
|
||||
`StatefulCopyable` to force all `DataAccess` implementations to implement `StatefulCopyable` as well. See [Understanding `StatefulCopyable`](#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 doesn't require serialization, like `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();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that the `getData()` method returns `this`, and the `DataAccess` implementation implements `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.
|
||||
|
||||
`Data` interface puts constraints on `equals()`, `hashCode()`, and `toString()` implementations. This is why `ByteArrayDataAccess` subclasses `AbstractData`, and inherits proper implementations from it.
|
||||
It is OK for serializer strategy implementation to 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 (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: No special coding is required to use this pair of serializers with `byte[]` keys of values in Chronicle Map. It is used as the default, if you configure `byte[]` key, or value type.
|
||||
|
||||
|
||||
'''
|
||||
<<CM_Tutorial.adoc#,Back to Tutorial>>
|
||||
Reference in New Issue
Block a user