Skip to content

Latest commit

 

History

History
156 lines (127 loc) · 5.48 KB

CM_Tutorial_Sized.adoc

File metadata and controls

156 lines (127 loc) · 5.48 KB

SizedWriter and SizedReader

This pair of interfaces is configured using ChronicleMapBuilder.keyMarshallers() or valueMarshallers() for the key, or value type, of the map, respectively. Overloaded methods; those for [BytesWriter and BytesReader](#byteswriter-and-bytesreader) interfaces have the same names).

The main two methods to implement in SizedWriter interface are:

  • long size(@NotNull T toWrite); returns the number of bytes, which is written by the subsequent write() method given the same toWrite instance of the serialized type.

  • void write(Bytes out, long size, @NotNull T toWrite); writes the given toWrite instance to the given out bytes sink. Additionally, size is provided, which is the value computed for the same toWrite instance by calling size() method. When SizedWriter is used internally, the size() method is always called before write(); caching logic in SizedWriter implementation may rely on this. This method should advance the writePosition() of the given out Bytes exactly by the given size number of bytes.

SizedReader’s `T read(Bytes in, long size, @Nullable T using); method is similar to the corresponding read() method in BytesReader interface, except that the size of the serialized object is provided.

This pair of interfaces is suitable, if:

  • The serialized form of the type effectively includes the size of the rest of the serialized form in the beginning.

  • The type is not a plain sequence of bytes; for example, byte[] or ByteBuffer. For byte[] or ByteBuffer, the DataAccess and SizedReader pair of interfaces is a much better choice.

Examples of such types include lists, and arrays of constant-sized elements.

Compared to BytesWriter and BytesReader, this pair of interfaces allows the sharing of information about the serialized form, between serialization logic, and the Chronicle Map; this saves a few bytes by storing the serialization size only once, rather than twice. This pair of interfaces is not much more difficult to implement, but the gains are also not large.

Example: Serializing lists of simple Point structures:

public final class Point {

    public static Point of(double x, double y) {
        Point p = new Point();
        p.x = x;
        p.y = y;
        return p;
    }

    double x, y;
}

Serializer implementation:

public final class PointListSizedMarshaller
        implements SizedReader<List<Point>>, SizedWriter<List<Point>>,
        ReadResolvable<PointListSizedMarshaller> {

    static final PointListSizedMarshaller INSTANCE = new PointListSizedMarshaller();

    private PointListSizedMarshaller() {}

    /** A point takes 16 bytes in serialized form: 8 bytes for both x and y value */
    private static final long ELEMENT_SIZE = 16;

    @Override
    public long size(@NotNull List<Point> toWrite) {
        return toWrite.size() * ELEMENT_SIZE;
    }

    @Override
    public void write(Bytes out, long size, @NotNull List<Point> toWrite) {
        toWrite.forEach(point -> {
            out.writeDouble(point.x);
            out.writeDouble(point.y);
        });
    }

    @NotNull
    @Override
    public List<Point> read(@NotNull Bytes in, long size, List<Point> using) {
        if (size % ELEMENT_SIZE != 0) {
            throw new IORuntimeException("Bytes size should be a multiple of " + ELEMENT_SIZE +
                    ", " + size + " read");
        }
        long listSizeAsLong = size / ELEMENT_SIZE;
        if (listSizeAsLong > Integer.MAX_VALUE) {
            throw new IORuntimeException("List size couldn't be more than " + Integer.MAX_VALUE +
                    ", " + listSizeAsLong + " read");
        }
        int listSize = (int) listSizeAsLong;
        if (using == null) {
            using = new ArrayList<>(listSize);
            for (int i = 0; i < listSize; i++) {
                using.add(null);
            }
        } else if (using.size() < listSize) {
            while (using.size() < listSize) {
                using.add(null);
            }
        } else if (using.size() > listSize) {
            using.subList(listSize, using.size()).clear();
        }
        for (int i = 0; i < listSize; i++) {
            Point point = using.get(i);
            if (point == null)
                using.set(i, point = new Point());
            point.x = in.readDouble();
            point.y = in.readDouble();
        }
        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 PointListSizedMarshaller readResolve() {
        return INSTANCE;
    }
}

Usage example:

try (ChronicleMa p<String, List<Point>> objects = ChronicleMap
        .of(String.class, (Class<List<Point>>) (Class) List.class)
        .averageKey("range")
        .valueMarshaller(PointListSizedMarshaller.INSTANCE)
        .averageValue(asList(of(0, 0), of(1, 1)))
        .entries(10)
        .create()) {
    objects.put("range", asList(of(0, 0), of(1, 1)));
    objects.put("square", asList(of(0, 0), of(0, 100), of(100, 100), of(100, 0)));

    Assert.assertEquals(2, objects.get("range").size());
    Assert.assertEquals(4, objects.get("square").size());
}