Kryo做为一个优秀的Java序列化方案,在网上能找到很多测评,但未见系统的中文入门或说明文档。官方文档是最好的学习文档。虽然英文不差,但啃下来毕竟没母语来的舒服。这里抽出时间作些翻译,以方便你们查阅。为阅读流畅,文中选择性的未翻译某些专业词汇,如 buffer、scheme等, 并在双尖括号中给出了译者注释,形如 << 注释... >> 。如遇到逻辑错误、阅读不通等,请参阅原文文档,并感谢您的指正。java
翻译的源官方文档更新于2017年5月,本文初次翻译于2017年5月底。git
官方文档地址:github
https://github.com/EsotericSoftware/kryo/blob/master/README.mdobjective-c
=============================如下为翻译正文=============================数据库
Kryo是一种快速高效的Java对象图(Object graph)序列化框架。 该项目的目标是速度、效率和易于使用的API。 当对象须要持久化时,不管是用于文件、数据库仍是经过网络,该项目都颇有用。<<译者注:Object graph >>apache
Kryo还能够执行自动深层浅层的复制/克隆。这是从对象直接复制到对象,而不是object-> bytes-> object。api
本文档适用于 Kryo v2.0 版本。 请参阅v1.x的 V1Documentation 。数组
若是您计划使用 Kryo 进行网络通讯, KryoNet 项目对您可能会有帮助。缓存
版本4.0.0为稳定性和性能带来了几项新功能和改进。如下是您应留意的一些亮点(此外,您还应仔细的研究变动日志并测试升级)。安全
重要提示:此改变会破坏 FieldSerializer 对于通用字段的序列化格式,所以使用 Kryo 3 和 FieldSerializer 序列化的通用类默认不能使用 Kryo 4 进行反序列化。为了反序列化 Kryo 3 序列化过的普通类数据,你必须设置 kryo.getFieldSerializerConfig().setOptimizedGenerics(true);! 您须要在各类案例中,测试序列化/反序列化的升级工做。
详细信息请参阅发布说明。
对于序列化库,最重要的评判标准是它能够反序列化之前的序列化数据。对于kryo,咱们也按照这个首要法则进行版本控制:
重定义规则 1.): 若是任何底层二进制格式被更改(参见 IO 和 Unsafe-based IO)或者经常使用序列化器生成的数据被改变了(例如一些默认的序列化器)。
重定义规则 2.): 因为java访问修饰符的限制,技术 api 比语义 api ( documented on this page ) 的做用域更广了。所以,在没有用户受到影响的状况下,技术上的二进制兼容性可能会被破坏。
<<译者注: 重定义规则 即 给出了规则1 和 规则2 的另外一种表述>>
请牢记,序列化库的任何一次升级都是重大事件。升级 kryo 时,请检查升级带来的全部改变,并用您的数据结构和环境设置进行完全的测试。
咱们尽可能使升级过程简易并安全:
Kryo JAR 可在发布页面和 Maven Central 上找到。 Kryo 的最新快照,包括 master 的快照构建,都在 Sonatype Repository 中。
要使用Kryo的官方版本,请在 pom.xml 中使用如下代码段
<dependency>
<groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>4.0.0</version> </dependency>
若是您由于在类路径中使用了不一样版本的 asm 而遇到问题,可使用包含这个 asm 版本的 kryo-shaded jar,并将其从新放置在不一样的包中:
<dependency>
<groupId>com.esotericsoftware</groupId> <artifactId>kryo-shaded</artifactId> <version>4.0.0</version> </dependency>
若是要测试 Kryo 的最新快照,请在 pom.xml 中使用如下代码片断
<repository>
<id>sonatype-snapshots</id> <name>sonatype snapshots repo</name> <url>https://oss.sonatype.org/content/repositories/snapshots</url> </repository> <dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>4.0.1-SNAPSHOT</version> </dependency>
若是您使用没有 Maven 的 Kryo,请注意 Kryo jar 文件有几个外部依赖关系,它们的JAR也须要添加到类路径中。这些依赖关系是 MinLog logging library 和 Objenesis library。
向上翻页以查看类库的使用:
Kryo kryo = new Kryo(); // ... Output output = new Output(new FileOutputStream("file.bin")); SomeClass someObject = ... kryo.writeObject(output, someObject); output.close(); // ... Input input = new Input(new FileInputStream("file.bin")); SomeClass someObject = kryo.readObject(input, SomeClass.class); input.close();
Kryo 类执行序列化。 输出和输入类处理缓冲字节,并可选地刷新到流(stream)。
本文档的其他部分详细介绍了它的工做原理和库的高级用法。
Output 类是将数据写入字节数组 buffer 的 OutputStream。若是须要字节数组,则能够直接获取和使用该 buffer 。若是已经给定了OutputStream,当 buffer 满时,它将刷新 bytes 到 stream。Output有许多方法能够有效地将基本类型和字符串写为 bytes 。它提供相似于DataOutputStream,BufferedOutputStream,FilterOutputStream 和 ByteArrayOutputStream 类的功能。
在写入 OutputStream 时 buffer 后,请确保调用 flush() 或 close(),以便将缓冲的字节写入底层流。
Input 类是从字节数组缓冲区读取数据的 InputStream 类。若是须要从字节数组中读取,能够直接设置该缓冲区 (buffer) 。若是给定了InputStream,当缓冲区用尽时,它将从流中填充缓冲区。Input 有许多方法能够高效地从 bytes 读取基本类型和字符串。它提供相似于DataInputStream,BufferedInputStream,FilterInputStream 和 ByteArrayInputStream 类的功能。
若是要读写字节数组 (bytes)之外的对象,只需提供相应的 InputStream 或 OutputStream 便可。
Kryo 提供了额外的基于 sun.misc.Unsafe 的 UnsafeInput,UnsafeOutput IO 类。由于他们来源于 Kryo 的 Input 和 Output 类,因此能够在支持sun.misc.Unsafe 的平台上进行替换。
当须要从直接内存 ByteBuffers 或非堆内存中进行序列化或反序列化,可用为其定制的专用类 UnsafeMemoryInput 和 UnsafeMemoryOutput,而没必要使用一般的 Input 和 Output 类。
基于应用程序的具体状况,使用 Unsafe-based IO 会显着地性能提高(有时可达一个数量级)。特别地,将大型原始数组做为对象的一部分进行序列化时,这种方式会有很大的帮助。
当涉及序列化数据的二进制格式时,不保证 Unsafe-based IO 与 Kryo 的输入和输出流彻底兼容。
这意味着 Unsafe-based IO 的输出流只能被 Unsafe-based IO 的输入流读取,而不能由一般的输入流读取。反之亦然,普通的输出流产生的数据不能被 Unsafe-based IO 的输入流正确读取。
只要序列化和反序列化都采用 Unsafe IO 流,而且都在相同的处理器架构(更准确地说是,整数和浮点类型的字节顺序和内部表示相同)上执行,就能保证(should be)数据安全。
在 X86 架构上对 Unsafe IO 进行过全面的测试。其余的处理器架构上没有以相同的程度进行测试。例如,咱们有收到过 SPARC 平台上的一些错误报告。
public class ColorSerializer extends Serializer<Color> { public void write (Kryo kryo, Output output, Color object) { output.writeInt(object.getRGB()); } public Color read (Kryo kryo, Input input, Class<Color> type) { return new Color(input.readInt(), true); } }
Serializer 有两种能够实现的方法。 write() 将该对象写入字节 (bytes)。 read() 建立对象的新实例,并用输入数据填充对象。
Kryo 实例可用于写入和读取嵌套对象。若是 Kryo 用于读取 read() 中的嵌套对象,且若是嵌套对象能够引用父对象,则必须先调用 kryo.reference() 引用父对象。若是嵌套对象不能引用父对象,或 Kryo 不被用于嵌套对象,或者没有使用引用,则没有必要调用 kryo.reference() 。若是嵌套对象可使用相同的serializer,那么 serializer 必须是可重入的。
代码不该该直接使用 serializers,而应该使用 Kryo 的读写方法。这将使 Kryo 来协调序列化并处理诸如引用和空对象的特征。
默认状况下,serializers 不须要处理为空的对象。 Kryo 框架将根据须要写一个字节,以表示 null 或非 null。若是一个 serializers 想要更高效地本身来处理 nulls,能够设置 Serializer#setAcceptsNull(true)。这个设置也能够在已知类型的全部实例永不为空时,来避免写入空标识字节。
当Kryo写出一个对象的实例时,首先可能须要写出一些标识对象类的东西。默认状况下,写入完整类名,而后写入该对象的字节。后续出现的同一类对象图的对象用变长的int来写(using a variable length int)。写类的名字有点低效,因此类能够事先注册:
Kryo kryo = new Kryo(); kryo.register(SomeClass.class); // ... Output output = ... SomeClass someObject = ... kryo.writeObject(output, someObject);
这里,SomeClass 注册到了 Kryo,它将该类与一个 int 型的 ID 相关联。当 Kryo 写出 SomeClass 的一个实例时,它会写出这个 int ID。这比写出类名更有效。在反序列化期间,注册的类必须具备序列化期间相同的 ID 。上面展现的注册方法分配下一个可用的最小整数 ID,这意味着类被注册的顺序十分重要。注册时也能够明确指定特定 ID,这样的话注册顺序就不重要了:
Kryo kryo = new Kryo(); kryo.register(SomeClass.class, 10); kryo.register(AnotherClass.class, 11); kryo.register(YetAnotherClass.class, 12);
当 IDs 是小的正整数时最有效。负数不能有效地序列化。 -1 和 -2 是保留值。<<译者注:-1 和-2 有其余含义>>
能够混合使用注册和未注册的类。默认使用 ID 0-9 注册全部基本类型,基本类包装器,String 和 void。因此要当心此范围内的注册覆盖的状况。
当 Kryo#setRegistrationRequired 设置为true,可在遇到任何未注册的类时抛出异常。这能阻止应用程序使用类名字符串来序列化。
若是使用未注册的类,则应考虑使用较短的包名。
写入类标识符后,Kryo使用一个序列化器(serializer) 来写入对象的字节。当类被注册后,serializer 实例就能被肯定了:
Kryo kryo = new Kryo(); kryo.register(SomeClass.class, new SomeSerializer()); kryo.register(AnotherClass.class, new AnotherSerializer());
若是一个类未注册或没有指定序列化器,则会自动从映射了类和序列化器的“ 默认序列化器 (default serializers)”列表中选择一个序列化器。如下类默认设置过序列化器:
能够另外添加其余的默认序列化器:
Kryo kryo = new Kryo(); kryo.addDefaultSerializer(SomeClass.class, SomeSerializer.class); // ... Output output = ... SomeClass someObject = ... kryo.writeObject(output, someObject);
也可使用 DefaultSerializer 注解:
@DefaultSerializer(SomeClassSerializer.class) public class SomeClass { // ... }
若是类没有匹配默认序列化器,那么默认状况下会使用 FieldSerializer 。这也能改变:
Kryo kryo = new Kryo(); kryo.setDefaultSerializer(AnotherGenericSerializer.class);
一些序列化器容许提供额外的信息,以减小输出字节数:
Kryo kryo = new Kryo(); FieldSerializer someClassSerializer = new FieldSerializer(kryo, SomeClass.class); CollectionSerializer listSerializer = new CollectionSerializer(); listSerializer.setElementClass(String.class, kryo.getSerializer(String.class)); listSerializer.setElementsCanBeNull(false); someClassSerializer.getField("list").setClass(LinkedList.class, listSerializer); kryo.register(SomeClass.class, someClassSerializer); // ... SomeClass someObject = ... someObject.list = new LinkedList(); someObject.list.add("thishitis"); someObject.list.add("bananas"); kryo.writeObject(output, someObject);
在这个例子中,FieldSerializer 用于 SomeClass 的序列化过程。由于配置了 FieldSerializer,因此“ list ”字段将始终为 LinkedList,并会使用指定的 CollectionSerializer。 由于配置了 CollectionSerializer,所以每一个元素将是一个String,且都不为 null。这会使序列化过程更有效率。这种状况下,列表中每一个元素节省了2到3个字节。
默认状况下,大多数类最终会使用 FieldSerializer。 它本质上是自动化的处理了手动的序列化过程。 FieldSerializer 会被直接分配给对象的字段。 若是这些字段是 public,protected,或默认的访问权限(package private),且不是final的,将采用字节码生成技术(bytecode generation)以求速度(参见 ReflectASM)。对于私有字段,使用 setAccessible 和缓存反射技术,速度也不慢。
也提供了其余通用的序列化器,如 BeanSerializer,TaggedFieldSerializer,CompatibleFieldSerializer 和 VersionFieldSerializer。更多的序列化器可在 github 和 kryo-serializer 上找到。
虽然 FieldSerializer 是大多数类的理想选择,但类也能够方便地进行本身的序列化。 这须要实现 KryoSerializable 接口(相似于 JDK 中的java.io.Externalizable 接口)。
public class SomeClass implements KryoSerializable { // ... public void write (Kryo kryo, Output output) { // ... } public void read (Kryo kryo, Input input) { // ... } }
虽然很是罕见,但有一些类不能被 Kryo 序列化。 在这种状况下,可使用 Kryo 的 JavaSerializer 提供后备解决方案,并使用标准Java序列化。只要对象能被Java序列化,就能采用这种方案。虽然这种方案与一般的 Java 序列化同样低效,但至少可以完成序列化工做。 固然,在这种方案中,正如一般的 Java 序列化所要求的那样,你的类须要实现 Serializable 或 Externalizable 接口。
当您的类实现了 Java 的 Serializable 接口,那么您就能使用 Kryo 专用的 JavaSerializer 序列化器:
kryo.register(SomeClass.class, new JavaSerializer());
若是您的类实现了Java的 Externalizable 接口,那么您就能使用 Kryo 专用的 ExternalizableSerializer 序列化器:
kryo.register(SomeClass.class, new ExternalizableSerializer());
典型地,当使用 FieldSerializer 时,它可以自动检测类的每一个字段应该使用哪一个序列化器。但在某些状况下,您可能会但愿更改默认规则并自定义某个字段的序列化。
为此,Kryo 提供了一组注解。 @Bind 可用于任何字段,@CollectionBind 用于类型为集合(collection)的字段,@MapBind 用于类型为 map 的字段:
public class SomeClass { // Use a StringSerializer for this field @Bind(StringSerializer.class) Object stringField; // Use a MapSerializer for this field. Keys should be serialized // using a StringSerializer, whereas values should be serialized // using IntArraySerializer @BindMap( valueSerializer = IntArraySerializer.class, keySerializer = StringSerializer.class, valueClass = int[].class, keyClass = String.class, keysCanBeNull = false) Map map; // Use a CollectionSerializer for this field. Elements should be serialized // using LongArraySerializer @BindCollection( elementSerializer = LongArraySerializer.class, elementClass = long[].class, elementsCanBeNull = false) Collection collection; // ... }
Kryo有三组读写对象的方法。
若是不知道对象的具体类,且对象能够为null:
kryo.writeClassAndObject(output, object);
// ... Object object = kryo.readClassAndObject(input); if (object instanceof SomeClass) { // ... }
若是类已知且对象能够为null:
kryo.writeObjectOrNull(output, someObject);
// ... SomeClass someObject = kryo.readObjectOrNull(input, SomeClass.class);
若是类已知且对象不能为null:
kryo.writeObject(output, someObject);
// ... SomeClass someObject = kryo.readObject(input, SomeClass.class);
默认状况下,图中每一个对象从第二个开始的表象都以整数顺序存储。这种方式能够序列化相同对象和循环图的多个引用。它具备少许的开销,若是不须要,能够禁用以节省空间:
Kryo kryo = new Kryo(); kryo.setReferences(false); // ...
当使用 Kryo 做为嵌套对象的序列化器时,必须在 read() 中调用 kryo.reference()。有关详细信息,请参阅 Serializers。
特定类型的序列化器使用 Java 代码建立该类型的新实例。序列化器如 FieldSerializer 是泛型的,用于处理建立任何类的新实例。默认状况下,若是某个类有一个无参构造方法,那么它将经过 ReflectASM 或反射来调用,不然抛出异常。若是无参构造方法是私有的,则尝试经过setAccessible用反射来访问它。这样的过程,可使 Kryo 在不影响公共API的状况下建立类的实例。
当不能使用 ReflectASM 或反射时,能够配置 Kryo 使用 InstantiatorStrategy 来建立类的实例。Objenesis 提供 StdInstantiatorStrategy,它使用JVM 特定的 API 来建立类的实例,而不会调用任何构造方法。虽然这适用于许多 JVM,但无参构造方法的移植性更好。
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
请注意,须要设计类来按下述的方式建立。若是指望调用一个类的构造函数,那么当经过这种机制建立时,它可能处于未初始化的状态。
在许多状况下,您可能但愿有这样的策略:Kryo 首先尝试使用无参构造方法,若是尝试失败,再尝试使用 StdInstantiatorStrategy 做为后备方案,由于后备方案不须要调用任何构造方法。这种策略的配置能够这样表示:
kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
然而,这样的默认行为须要一个无参构造方法。
Objenesis 还可使用Java的内置序列化机制建立新对象。如此的话,类必须实现 java.io.Serializable,且在调用时会执行父类中的第一个无参构造方法。
kryo.setInstantiatorStrategy(new SerializingInstantiatorStrategy());
您也能够编写本身的 InstantiatorStrategy。
要定制特定类型的建立方式,能够设置一个 ObjectInstantiator。这会覆盖 ReflectASM,反射和 InstantiatorStrategy。
Registration registration = kryo.register(SomeClass.class); registration.setObjectInstantiator(...);
另外,一些序列化器提供重写的方式定制对象的建立。
kryo.register(SomeClass.class, new FieldSerializer(kryo, SomeClass.class) { public Object create (Kryo kryo, Input input, Class type) { return new SomeClass("some constructor arguments", 1234); } });
序列化库须要关于如何建立新实例、获取和设置值、导航对象图等的特定信息。这些几乎是复制对象所需的一些,所以 Kryo 支持自动生成深和浅的对象副本。注意 Kryo 的复制不会序列化为字节而后反转,它使用直接分配。
Kryo kryo = new Kryo(); SomeClass someObject = ... SomeClass copy1 = kryo.copy(someObject); SomeClass copy2 = kryo.copyShallow(someObject);
Serializer 类有一个复制方法能够完成复制工做。若是实现了特定于应用程序的序列化器而不使用复制功能时,能够忽略这些方法。 Kryo 提供的全部序列化器都支持复制。多个对同一个对象的引用和循环引用由框架自动处理。
与 read() Serializer 方法相似,必须先调用 kryo.reference(),而后才能使用 Kryo 来复制子对象。有关详细信息,请参阅序列化器。
相似于 KryoSerializable,类能够实现 KryoCopyable 进行本身的复制:
public class SomeClass implements KryoCopyable<SomeClass> { // ... public SomeClass copy (Kryo kryo) { // Create new instance and copy values from this instance. } }
Kryo 有两种上下文方法。 getContext() 返回一个用于存储用户数据的 map。 因为 Kryo 实例可用于全部序列化器,所以此数据能够随时得到。 getGraphContext() 与之相似,但在每一个对象图被序列化或反序列化以后被清除。 这样能够方便地管理每一个对象图状态。
Kryo 支持流,所以在全部序列化字节上使用压缩或加密是不太必要的:
OutputStream outputStream = new DeflaterOutputStream(new FileOutputStream("file.bin")); Output output = new Output(outputStream); Kryo kryo = new Kryo(); kryo.writeObject(output, object); output.close();
若有须要,可使用序列化器来压缩或加密对象图字节中的一个部分字节。 例如,请参阅 DeflateSerializer 或 BlowfishSerializer。 这些序列化器包装了另外一个序列化器,并对字节进行编码和解码。
有时先写一些数据的长度,而后再写入数据的机制是颇有用的。若是数据长度不能提早知道,则须要缓冲全部数据以肯定其长度,而后写入长度,再而后写入数据。这种缓冲能防止流式传输而且潜在地须要很是大的缓冲区(buffer),这并不理想。
分块编码经过使用小的缓冲区(buffer)来解决这个问题。当缓冲区已满时,其长度被写入,而后是数据。这是一个数据块的机制。当多个块时,缓冲区被清除,这样继续直到没有更多的数据写入。长度为零的块表示块的结尾。
Kryo 提供了简单的分块编码的类。 OutputChunked 用于写分块数据。它扩展了 Output,因此有方便的方法来写入数据。当 OutputChunked 缓冲区已满时,它将该块刷新到包装的 OutputStream。 endChunks() 方法用于标记一组块的结尾。
OutputStream outputStream = new FileOutputStream("file.bin"); OutputChunked output = new OutputChunked(outputStream, 1024); // Write data to output... output.endChunks(); // Write more data to output... output.endChunks(); // Write even more data to output... output.close();
如要读取分块数据,使用 InputChunked。它继承了 Input,因此有方法来读取数据。当读取时,InputChunked 将在到达一组块的末尾时做为数据的末尾。nextChunks() 方法前进到下一组块,即便当前块组中的数据并未读取完。
InputStream outputStream = new FileInputStream("file.bin"); InputChunked input = new InputChunked(inputStream, 1024); // Read data from first set of chunks... input.nextChunks(); // Read data from second set of chunks... input.nextChunks(); // Read data from third set of chunks... input.close();
对于某些需求,特别是长期存储序列化后的bytes,序列化如何处理类的变化相当重要。这被称为 forward(读取较新类的序列化生成的字节)和 backword(读取由旧类序列化产生的字节)兼容性。
FieldSerializer 是最经常使用的 serializer。它是通用的,能够序列化大多数类而无需任何配置。它是高效的,只写字段数据,没有任何额外信息。它不支持添加,删除或更改字段类型,不会使先前的序列化字节无效。大多状况下,这能够接受,例如经过网络发送数据,可是做为长期数据存储,这不是个好方法,由于Java 类不能演化。因为 FieldSerializer 默认尝试读写非 public 字段,所以评估将被序列化的每一个类是很重要的工做。
当没有指定序列化程序时,默认状况下使用 FieldSerializer。若有必要,可使用另外一种通用的序列化器:
kryo.setDefaultSerializer(TaggedFieldSerializer.class);
BeanSerializer 很是相似于 FieldSerializer,除了它使用 bean getter 和 setter 方法,而不是直接的字段访问。速度上来讲,这稍慢些,但由于它使用公共 API 来配置对象,可能会更安全。
VersionFieldSerializer 扩展了 FieldSerializer,并容许字段具备 @Since(int) 注解来指示它们被添加的版本。对于特定字段,@Since 中的值不该该在建立后改变。这不如 FieldSerializer 那么灵活,后者能够处理大多数类而不须要注解,但前者提供向后兼容性。这意味着能够添加新的字段,但删除,重命名或更改任何字段的类型将使先前的序列化字节失效。与 FieldSerializer 相比,VersionFieldSerializer 具备很是少的开销(一个额外的变量)。
TaggedFieldSerializer 将 FieldSerializer 扩展为仅序列化具备 @Tag(int) 注解的字段,提供向后兼容性,从而能够添加新字段。而且它还经过setIgnoreUnknownTags(true) 提供向前兼容性,所以任何未知的字段 tags 将被忽略。 对比 VersionFieldSerializer,TaggedFieldSerializer 有两个优势:1)字段能够被重命名,2)标记有 @Deprecated 注解的字段,在读取旧字节时或写出新字节时将被忽略。尽管字段和 @Tag 注解必须保留在类中,弃用机制有效地从序列化中删除了废弃的字段。废弃的字段能被设置私有和/或重命名,这样他们不会弄乱类的信息(例如,ignored, ignored2)。基于这些缘由,TaggedFieldSerializer 为类的演化提供更多的灵活性。缺点是与 VersionFieldSerializer(每一个字段需额外的一个变量)相比,它具备少许额外的开销。
CompatibleFieldSerializer 扩展了 FieldSerializer 以提供向前和向后兼容性,这意味着能够添加或删除字段,而不会使先前的序列化字节无效。它不支持更改字段的类型。像 FieldSerializer 同样,它能够序列化大多数类而不须要注解。前向和后向兼容性有一些代价:在序列化中第一次遇到某个类时,会写入一个包含字段名称字符串的简单 scheme。同时,在序列化和反序列化期间,缓冲区用以执行分块编码。这种机制使得CompatibleFieldSerializer 可以忽略它不认识的字段的字节。当 Kryo 配置为使用引用时,若是某个字段被删除,可能引起CompatibleFieldSerializer 的问题。若是您的类继承层次结构包含相同的命名字段,请使用 CachedFieldNameStrategy.EXTENDED 策略。
class A { String a; } class B extends A { String a; } ... // use `EXTENDED` name strategy, otherwise serialized object can't be deserialized correctly. Attention, `EXTENDED` strategy increases the serialized footprint. kryo.getFieldSerializerConfig().setCachedFieldNameStrategy(FieldSerializer.CachedFieldNameStrategy.EXTENDED);
能够轻松开发额外的序列化器,用于向前和向后兼容性,例如使用外部手写 schema 的序列化器。
提供的 Kryo 序列化器默认假设将用 Java 反序列化,所以它们不会明肯定义写入的格式。序列化器可使用更容易被其余语言读取的标准格式写入,但默认状况下不提供。
序列化器 Kryo 在序列化嵌套对象时使用调用堆栈。 Kryo 使用最小的堆栈调用,但对于极深的对象图,可能会发生堆栈溢出。这是大多数序列化库的常见问题,包括内置的 Java 序列化。可使用 -Xss 增长堆栈大小,但请注意,这项配置做用于全部线程。JVM 中具备多个线程且巨大的堆栈大小会致使占用大量内存。
Kryo 不是线程安全的。每一个线程都应该有本身的 Kryo,Input 和 Output 实例。此外, bytes[] Input 可能被修改,而后在反序列化期间回到初始状态,所以不该该在多线程中并发使用相同的 bytes[]。
由于 Kryo 实例的建立/初始化是至关昂贵的,因此在多线程的状况下,您应该池化 Kryo 实例。一个很是简单的解决方案是使用 ThreadLocal 将 Kryo实例绑定到 Threads,以下所示:
// Setup ThreadLocal of Kryo instances private static final ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() { protected Kryo initialValue() { Kryo kryo = new Kryo(); // configure kryo instance, customize settings return kryo; }; }; // Somewhere else, use Kryo Kryo k = kryos.get(); ...
或者您也可使用 kryo 提供的 KryoPool。 KryoPool 容许使用 SoftReferences 保留对 Kryo 实例的引用,这样当 JVM 开始耗尽内存时,Kryo 实例就能够被 GC 回收(固然你也可使用 ThreadLocal 和 SoftReferences)。
如下是一个示例,显示如何使用KryoPool:
import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.pool.*; KryoFactory factory = new KryoFactory() { public Kryo create () { Kryo kryo = new Kryo(); // configure kryo instance, customize settings return kryo; } }; // Build pool with SoftReferences enabled (optional) KryoPool pool = new KryoPool.Builder(factory).softReferences().build(); Kryo kryo = pool.borrow(); // do s.th. with kryo here, and afterwards release it pool.release(kryo); // or use a callback to work with kryo - no need to borrow/release, // that's done by `run`. String value = pool.run(new KryoCallback() { public String execute(Kryo kryo) { return kryo.readObject(input, String.class); } });
Kryo利用低开销,轻量级的MinLog日志库。能够经过如下方法之一设置日志记录级别:
Log.ERROR(); Log.WARN(); Log.INFO(); Log.DEBUG(); Log.TRACE();
Kryo在INFO(默认)和以上级别没有记录。 DEBUG方便在开发过程当中使用。调试一个特定的问题时,TRACE很好用,可是一般输出的信息太多。
MinLog支持固定的日志记录级别,这会致使javac在编译时删除低于该级别的日志记录。在Kryo发行版ZIP中,"debug"JAR启用日志记录。 "production"JAR使用NONE的固定日志记录级别,这意味着全部日志记录代码已被删除。
请参阅如下为Scala类提供序列化的项目:
请参阅如下项目,它是Kryo的Objective-C端口:
能够将 Kryo 与 JVM Serializers 项目中的许多其余序列化库进行比较。使用基准测试很难完全比较序列化库。这些序列化库有不一样的目的,并擅长彻底不一样的问题。为了理解这些基准测试,运行代码和序列化的数据应该根据您的具体需求进行分析和对比。一些序列化程序是高度优化的,代码可能多达几页,而有些仅只有几行。这很好的说明了有些测试可能有用,但在许多状况下可能并不实用。
“kryo”是典型的 Kryo 使用方式,类是注册的,序列化自动完成。 “kryo-opt”显示了如何配置序列化器以减小序列化数据的大小,但序列化仍然是自动完成的。 “kryo-manual”寿命了如何使用手写的序列化代码来优化大小和速度,同时仍然利用 Kryo 进行大部分工做。
有很多项目使用了Kryo,如下仅列出一些。若是您但愿您的项目也列在此,请提交一个申请。