diff --git a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java new file mode 100644 index 000000000..1165bcec0 --- /dev/null +++ b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/ArrayClassResolverBenchmark.java @@ -0,0 +1,154 @@ +/* Copyright (c) 2008-2022, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.esotericsoftware.kryo.benchmarks; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.util.ArrayClassResolver; +import org.openjdk.jmh.annotations.*; + +import java.util.*; + +import static com.esotericsoftware.kryo.benchmarks.FieldSerializerBenchmark.*; + +/** + * {@link ArrayClassResolver} is fast especially in {@link #deserializeCollection(DeserializingCollectionWithArrayClassResolverState)}}. + * + *
+ * Benchmark                                           Mode  Cnt     Score   Error  Units
+ * ArrayClassResolverBenchmark.deserializeCollection  thrpt       4125.996          ops/s +17%
+ * FieldSerializerBenchmark.deserializeCollection     thrpt       3511.014          ops/s
+ *
+ * Comparing only readClass() by profiler.
+ * 									Total Time	Invocations
+ * DefaultClassResolver.readClass()	19,872 ms 	10,006,631
+ * ArrayClassResolver.readClass()	12,371 ms 	10,029,051		60% faster
+ * 
+ * + * @author lifeinwild1@gmail.com + */ +public class ArrayClassResolverBenchmark { + @Benchmark + public void field (FieldSerializerStateArray state) { + state.roundTrip(); + } + + @Benchmark + public void compatible (CompatibleStateArray state) { + state.roundTrip(); + } + + @Benchmark + public void tagged (TaggedStateArray state) { + state.roundTrip(); + } + + @Benchmark + public void version (VersionStateArray state) { + state.roundTrip(); + } + + @Benchmark + public void custom (CustomStateArray state) { + state.roundTrip(); + } + + @Benchmark + public void deserializeCollection(DeserializingCollectionWithArrayClassResolverState state) { state.roundTrip(); } + + // + + public static Kryo createKryoArray(){ + return new Kryo(new ArrayClassResolver(), null); + } + + static public class FieldSerializerStateArray extends FieldSerializerState { + @Override + public Kryo createKryo() { + return createKryoArray(); + } + } + + static public class CompatibleStateArray extends CompatibleState { + @Override + public Kryo createKryo() { + return createKryoArray(); + } + } + + static public class TaggedStateArray extends TaggedState { + @Override + public Kryo createKryo() { + return createKryoArray(); + } + } + + static public class VersionStateArray extends VersionState { + @Override + public Kryo createKryo() { + return createKryoArray(); + } + } + + static public class CustomStateArray extends CustomState { + @Override + public Kryo createKryo() { + return createKryoArray(); + } + } + + static public class DeserializingCollectionWithArrayClassResolverState extends DeserializingCollectionState { + @Override + protected Kryo createKryo() { + return createKryoArray(); + } + } + + @State(Scope.Thread) + static public abstract class DeserializingCollectionState{ + final Kryo kryo = createKryo(); + final Output output = new Output(1024 * 1024); + final Input input = new Input(output.getBuffer()); + Object object; + + abstract protected Kryo createKryo(); + + @Setup(Level.Trial) + public void setup () { + HashMap m = new HashMap<>(); + for(long i=0;i<2000;i++) + m.put(i, "val"); + + object = m; + + kryo.register(HashMap.class); + + output.setPosition(0); + kryo.writeObject(output, object); + } + + public void roundTrip() { + input.setPosition(0); + input.setLimit(output.position()); + kryo.readObject(input, object.getClass()); + } + } +} diff --git a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java index 491c98f6c..41ff0a50c 100644 --- a/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java +++ b/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/FieldSerializerBenchmark.java @@ -70,6 +70,9 @@ public void custom (CustomState state) { state.roundTrip(); } + @Benchmark + public void deserializeCollection(DeserializingCollectionWithDefaultClassResolverState state) { state.roundTrip(); } + // @State(Scope.Thread) @@ -77,11 +80,15 @@ static public abstract class BenchmarkState { @Param({"true", "false"}) public boolean references; @Param() public ObjectType objectType; - final Kryo kryo = new Kryo(); + final Kryo kryo = createKryo(); final Output output = new Output(1024 * 512); final Input input = new Input(output.getBuffer()); Object object; + public Kryo createKryo(){ + return new Kryo(); + } + @Setup(Level.Trial) public void setup () { switch (objectType) { @@ -310,4 +317,11 @@ public void write (Kryo kryo, Output output, Image image) { kryo.writeObjectOrNull(output, image.media, Media.class); } } + + static public class DeserializingCollectionWithDefaultClassResolverState extends ArrayClassResolverBenchmark.DeserializingCollectionState { + @Override + protected Kryo createKryo() { + return new Kryo(); + } + } } diff --git a/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java new file mode 100644 index 000000000..57d0a2b40 --- /dev/null +++ b/src/com/esotericsoftware/kryo/util/ArrayClassResolver.java @@ -0,0 +1,218 @@ +/* Copyright (c) 2008-2022, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.esotericsoftware.kryo.util; + +import com.esotericsoftware.kryo.ClassResolver; +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.KryoException; +import com.esotericsoftware.kryo.Registration; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +import static com.esotericsoftware.kryo.util.Util.*; +import static com.esotericsoftware.minlog.Log.*; + +/** This is enhanced resolver from {@link DefaultClassResolver} for fast deserialization of collections. + * + * @author lifeinwild1@gmail.com + * @apiNote In terms of functionality, {@link ArrayClassResolver} is completely equivalent to {@link DefaultClassResolver}. So the + * output binary is equivalent to that of {@link DefaultClassResolver}. + * @implNote You can specify the mappings between class and ID by {@link Kryo#register(Class, int)}, But don't specify huge ID + * like 20000000 because this resolver uses array internally. This resolver internally reconstructs array of + * {@link IntToObjArray} when the mappings are added. Therefore, it is not suitable in terms of performance if the + * mappings are added frequently at peaktime of application. Use the {@link Pool}. + * @see Pool */ +public class ArrayClassResolver implements ClassResolver { + protected final IdentityMap classToRegistration = new IdentityMap<>(); + private final IntToObjArray idToRegistrationTmp = new IntToObjArray<>(Registration.class); + protected Kryo kryo; + protected IdentityObjectIntMap classToNameId; + protected IntToObjArray nameIdToClass; + protected ObjectMap nameToClass; + protected int nextNameId; + + private int memoizedClassId = -1; + private Registration memoizedClassIdValue; + private Class memoizedClass; + private Registration memoizedClassValue; + + public void setKryo (Kryo kryo) { + this.kryo = kryo; + } + + public Registration register (Registration registration) { + memoizedClassId = -1; + memoizedClass = null; + if (registration == null) throw new IllegalArgumentException("registration cannot be null."); + if (registration.getId() != DefaultClassResolver.NAME) { + if (TRACE) { + trace("kryo", "Register class ID " + registration.getId() + ": " + className(registration.getType()) + " (" + + registration.getSerializer().getClass().getName() + ")"); + } + idToRegistrationTmp.put(registration.getId(), registration); + } else if (TRACE) { + trace("kryo", "Register class name: " + className(registration.getType()) + " (" + + registration.getSerializer().getClass().getName() + ")"); + } + classToRegistration.put(registration.getType(), registration); + Class wrapperClass = getWrapperClass(registration.getType()); + if (wrapperClass != registration.getType()) classToRegistration.put(wrapperClass, registration); + return registration; + } + + public Registration registerImplicit (Class type) { + return register(new Registration(type, kryo.getDefaultSerializer(type), DefaultClassResolver.NAME)); + } + + public Registration getRegistration (Class type) { + if (type == memoizedClass) return memoizedClassValue; + Registration registration = classToRegistration.get(type); + if (registration != null) { + memoizedClass = type; + memoizedClassValue = registration; + } + return registration; + } + + public Registration writeClass (Output output, Class type) { + if (type == null) { + if (TRACE || (DEBUG && kryo.getDepth() == 1)) log("Write", null, output.position()); + output.writeByte(Kryo.NULL); + return null; + } + Registration registration = kryo.getRegistration(type); + if (registration.getId() == DefaultClassResolver.NAME) + writeName(output, type, registration); + else { + if (TRACE) trace("kryo", "Write class " + registration.getId() + ": " + className(type) + pos(output.position())); + output.writeVarInt(registration.getId() + 2, true); + } + return registration; + } + + protected void writeName (Output output, Class type, Registration registration) { + output.writeByte(1); // NAME + 2 + if (classToNameId != null) { + int nameId = classToNameId.get(type, -1); + if (nameId != -1) { + if (TRACE) trace("kryo", "Write class name reference " + nameId + ": " + className(type) + pos(output.position())); + output.writeVarInt(nameId, true); + return; + } + } + // Only write the class name the first time encountered in object graph. + if (TRACE) trace("kryo", "Write class name: " + className(type) + pos(output.position())); + int nameId = nextNameId++; + if (classToNameId == null) classToNameId = new IdentityObjectIntMap<>(); + classToNameId.put(type, nameId); + output.writeVarInt(nameId, true); + if (registration.isTypeNameAscii()) + output.writeAscii(type.getName()); + else + output.writeString(type.getName()); + } + + protected Registration readName (Input input) { + int nameId = input.readVarInt(true); + if (nameIdToClass == null) nameIdToClass = new IntToObjArray<>(Class.class, nameId + 20); + Class type = nameIdToClass.get(nameId); + if (type == null) { + // Only read the class name the first time encountered in object graph. + String className = input.readString(); + type = getTypeByName(className); + if (type == null) { + try { + type = Class.forName(className, false, kryo.getClassLoader()); + } catch (ClassNotFoundException ex) { + // Fallback to Kryo's class loader. + try { + type = Class.forName(className, false, Kryo.class.getClassLoader()); + } catch (ClassNotFoundException ex2) { + throw new KryoException("Unable to find class: " + className, ex); + } + } + if (nameToClass == null) nameToClass = new ObjectMap<>(); + nameToClass.put(className, type); + } + nameIdToClass.put(nameId, type); + if (TRACE) trace("kryo", "Read class name: " + className + pos(input.position())); + } else { + if (TRACE) trace("kryo", "Read class name reference " + nameId + ": " + className(type) + pos(input.position())); + } + return kryo.getRegistration(type); + } + + protected Class getTypeByName (final String className) { + return nameToClass != null ? nameToClass.get(className) : null; + } + + public void reset () { + if (!kryo.isRegistrationRequired()) { + if (classToNameId != null) classToNameId.clear(2048); + if (nameIdToClass != null) nameIdToClass.clear(); + nextNameId = 0; + } + } + + @Override + public final Registration getRegistration (int classID) { + return idToRegistrationTmp.get(classID); + } + + @Override + public Registration readClass (Input input) { + int classID = input.readVarInt(true); + switch (classID) { + case Kryo.NULL: + if (TRACE || (DEBUG && kryo.getDepth() == 1)) log("Read", null, input.position()); + return null; + case DefaultClassResolver.NAME + 2: // Offset for NAME and NULL. + return readName(input); + } + + if (classID == memoizedClassId) { + if (TRACE) trace("kryo", + "Read class " + (classID - 2) + ": " + className(memoizedClassIdValue.getType()) + pos(input.position())); + return memoizedClassIdValue; + } + + int index = classID - 2; + Registration registration = getRegistration(index); + if (registration == null) throw new KryoException("Encountered unregistered class ID: " + (classID - 2)); + if (TRACE) trace("kryo", "Read class " + (classID - 2) + ": " + className(registration.getType()) + pos(input.position())); + + memoizedClassId = classID; + memoizedClassIdValue = registration; + + return registration; + } + + public Registration unregister (int classID) { + Registration registration = idToRegistrationTmp.remove(classID); + if (registration != null) { + classToRegistration.remove(registration.getType()); + memoizedClassId = -1; + memoizedClass = null; + Class wrapperClass = getWrapperClass(registration.getType()); + if (wrapperClass != registration.getType()) classToRegistration.remove(wrapperClass); + } + return registration; + } +} diff --git a/src/com/esotericsoftware/kryo/util/IntToObjArray.java b/src/com/esotericsoftware/kryo/util/IntToObjArray.java new file mode 100644 index 000000000..284ce52aa --- /dev/null +++ b/src/com/esotericsoftware/kryo/util/IntToObjArray.java @@ -0,0 +1,84 @@ +/* Copyright (c) 2008-2022, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.esotericsoftware.kryo.util; + +import java.lang.reflect.Array; + +/** LUT by array. + * + * @apiNote don't specify large int to key by {@link #put(int, Object)}. + * @author lifeinwild1@gmail.com */ +public final class IntToObjArray { + private final Class valueType; + private final int initialCapacity; + private final float expandRate; + private E[] array; + + public final E get (int key) { + if (key >= array.length || key < 0) { + return null; + } + return array[key]; + } + + IntToObjArray (Class valueType, int initialCapacity, float expansionRate) { + if (expansionRate <= 1.0) + throw new IllegalArgumentException("expansionRate <= 1.0"); + if (initialCapacity <= 0) + throw new IllegalArgumentException("initialCapacity <= 0"); + this.valueType = valueType; + array = (E[])Array.newInstance(valueType, initialCapacity); + this.initialCapacity = initialCapacity; + this.expandRate = expansionRate; + } + + IntToObjArray (Class valueType, int initialCapacity) { + this(valueType, initialCapacity, 1.1f); + } + + IntToObjArray (Class valueType) { + this(valueType, 1000); + } + + public void clear () { + array = (E[])Array.newInstance(valueType, initialCapacity); + } + + public E remove (int classid) { + if (classid >= array.length) + return null; + E r = array[classid]; + array[classid] = null; + return r; + } + + public E put (int classid, E v) { + if (classid >= array.length && array.length < Integer.MAX_VALUE) { + int nextSize = (int)(classid * expandRate); + E[] next = (E[])Array.newInstance(valueType, nextSize); + System.arraycopy(array, 0, next, 0, array.length); + array = next; + } + + E r = array[classid]; + array[classid] = v; + return r; + } +} diff --git a/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java b/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java new file mode 100644 index 000000000..e10da35ee --- /dev/null +++ b/test/com/esotericsoftware/kryo/util/ArrayClassResolverTest.java @@ -0,0 +1,146 @@ +/* Copyright (c) 2008-2022, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.esotericsoftware.kryo.util; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.KryoTestCase; +import com.esotericsoftware.kryo.Registration; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * You can test {@link ArrayClassResolver} also by below method: + * temporarily modify {@link KryoTestCase#setUp()}, then run all test cases. + * + *
+ * @BeforeEach
+ * public void setUp () throws Exception {
+ *	 if (debug && WARN) warn("*** DEBUG TEST ***");
+ *
+ * 	 kryo = new Kryo(new ArrayClassResolver(), null);
+ * }
+ * 
+ * + * @author lifeinwild1@gmail.com + */ +public class ArrayClassResolverTest extends KryoTestCase { + @BeforeEach + public void setUp () throws Exception { + super.setUp(); + + ArrayClassResolver resolver = new ArrayClassResolver(); + kryo = new Kryo(resolver, null); + kryo.register(ArrayList.class); + } + + @Test + void testHugeID () { + ArrayClassResolver resolver = new ArrayClassResolver(); + + int id0 = 0; + Registration input0 = new Registration(TestModel0.class, new TestSerializer0(), id0); + resolver.register(input0); + + int id1 = 1; + Registration input1 = new Registration(TestModel1.class, new TestSerializer1(), id1); + resolver.register(input1); + + int id1000000 = 1000000; + Registration input1000000 = new Registration(TestModel1000000.class, new TestSerializer1000000(), id1000000); + resolver.register(input1000000); + + Registration r0 = resolver.getRegistration(id0); + assertEquals(input0, r0); + + Registration r1 = resolver.getRegistration(id1); + assertEquals(input1, r1); + + Registration r1000000 = resolver.getRegistration(id1000000); + assertEquals(input1000000, r1000000); + } + + private static class TestModel1{ + + } + private static class TestSerializer1 extends Serializer { + + @Override + public void write(Kryo kryo, Output output, TestModel1 object) { + + } + + @Override + public TestModel1 read(Kryo kryo, Input input, Class type) { + return null; + } + } + + private static class TestModel0{ + + } + private static class TestSerializer0 extends Serializer { + + @Override + public void write(Kryo kryo, Output output, TestModel0 object) { + + } + + @Override + public TestModel0 read(Kryo kryo, Input input, Class type) { + return null; + } + } + private static class TestModel1000000{ + + } + private static class TestSerializer1000000 extends Serializer { + + @Override + public void write(Kryo kryo, Output output, TestModel1000000 object) { + + } + + @Override + public TestModel1000000 read(Kryo kryo, Input input, Class type) { + return null; + } + } + @Test + void testArrayList () { + ArrayList test = new ArrayList(); + test.add("one"); + test.add("two"); + test.add("three"); + + ArrayList copy = kryo.copy(test); + assertNotSame(test, copy); + assertEquals(test, copy); + } + + +}