Kryo 是一个快速高效的 Java 二进制对象图序列化框架。该项目的目标是高速、小尺寸和易于使用的 API。任何时候需要持久保存对象时,无论是保存到文件、数据库还是通过网络,该项目都很有用。
Kryo 还可以执行自动深浅复制/克隆。这是从对象到对象的直接复制,而不是对象到字节到对象的复制。
本文档适用于 Kryo 版本 5.x。请参阅 Wiki 了解版本 4.x。
请使用 Kryo 邮件列表来寻求问题、讨论和支持。请将 Kryo 问题跟踪器的使用限制为错误和增强功能,而不是问题、讨论或支持。
Kryo 发布两种工件/jar:
Kryo JAR 可在发布页面和 Maven Central 上找到。 Kryo 的最新快照(包括 master 的快照版本)位于 Sonatype 存储库中。
要在应用程序中使用最新的 Kryo 版本,请在pom.xml
中使用此依赖项条目:
< dependency >
< groupId >com.esotericsoftware</ groupId >
< artifactId >kryo</ artifactId >
< version >5.6.2</ version >
</ dependency >
要在要发布的库中使用最新的 Kryo 版本,请在pom.xml
中使用此依赖项条目:
< dependency >
< groupId >com.esotericsoftware.kryo</ groupId >
< artifactId >kryo5</ artifactId >
< version >5.6.2</ version >
</ dependency >
要使用最新的 Kryo 快照,请使用:
< repository >
< id >sonatype-snapshots</ id >
< name >sonatype snapshots repo</ name >
< url >https://oss.sonatype.org/content/repositories/snapshots</ url >
</ repository >
<!-- for usage in an application: -->
< dependency >
< groupId >com.esotericsoftware</ groupId >
< artifactId >kryo</ artifactId >
< version >5.6.3-SNAPSHOT</ version >
</ dependency >
<!-- for usage in a library that should be published: -->
< dependency >
< groupId >com.esotericsoftware.kryo</ groupId >
< artifactId >kryo5</ artifactId >
< version >5.6.3-SNAPSHOT</ version >
</ dependency >
并不是每个人都是 Maven 粉丝。在没有 Maven 的情况下使用 Kryo 需要将 Kryo JAR 以及 lib 中找到的依赖项 JAR 放在类路径上。
从源代码构建 Kryo 需要 JDK11+ 和 Maven。要构建所有工件,请运行:
mvn clean && mvn install
继续展示如何使用该库:
import com . esotericsoftware . kryo . Kryo ;
import com . esotericsoftware . kryo . io . Input ;
import com . esotericsoftware . kryo . io . Output ;
import java . io .*;
public class HelloKryo {
static public void main ( String [] args ) throws Exception {
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class );
SomeClass object = new SomeClass ();
object . value = "Hello Kryo!" ;
Output output = new Output ( new FileOutputStream ( "file.bin" ));
kryo . writeObject ( output , object );
output . close ();
Input input = new Input ( new FileInputStream ( "file.bin" ));
SomeClass object2 = kryo . readObject ( input , SomeClass . class );
input . close ();
}
static public class SomeClass {
String value ;
}
}
Kryo 类自动执行序列化。输出和输入类处理缓冲字节并可选择刷新到流。
本文档的其余部分详细介绍了其工作原理以及该库的高级用法。
使用输入和输出类将数据传入和传出 Kryo。这些类不是线程安全的。
Output 类是将数据写入字节数组缓冲区的 OutputStream。如果需要字节数组,可以直接获取并使用该缓冲区。如果为 Output 提供一个 OutputStream,则当缓冲区已满时,它会将字节刷新到流中,否则 Output 可以自动增长其缓冲区。 Output 有许多方法可以有效地将原语和字符串写入字节。它提供与 DataOutputStream、BufferedOutputStream、FilterOutputStream 和 ByteArrayOutputStream 类似的功能,全部在一个类中。
提示:Output 和Input 提供ByteArrayOutputStream 的所有功能。很少有理由将输出刷新到 ByteArrayOutputStream。
Output 在写入 OutputStream 时会缓冲字节,因此写入完成后必须调用flush
或close
才能将缓冲的字节写入 OutputStream。如果Output 尚未提供OutputStream,则无需调用flush
或close
。与许多流不同,Output 实例可以通过设置位置或设置新的字节数组或流来重用。
提示:由于输出已缓冲,因此没有理由将输出刷新到 BufferedOutputStream。
零参数输出构造函数创建一个未初始化的输出。在使用输出之前必须调用输出setBuffer
。
Input 类是一个从字节数组缓冲区读取数据的InputStream。如果需要从字节数组中读取,可以直接设置该缓冲区。如果输入有一个InputStream,当缓冲区中的所有数据都被读取后,它将从流中填充缓冲区。输入有许多方法可以有效地从字节读取原语和字符串。它提供与 DataInputStream、BufferedInputStream、FilterInputStream 和 ByteArrayInputStream 类似的功能,所有功能都在一个类中。
提示:Input 提供了 ByteArrayInputStream 的所有功能。很少有理由从 ByteArrayInputStream 读取输入。
如果调用了Input close
,则关闭该Input 的InputStream(如果有)。如果不从 InputStream 读取,则无需调用close
。与许多流不同,Input 实例可以通过设置位置和限制或设置新的字节数组或 InputStream 来重用。
零参数输入构造函数创建一个未初始化的输入。在使用输入之前必须调用输入setBuffer
。
ByteBufferOutput 和 ByteBufferInput 类的工作方式与 Output 和 Input 完全相同,只是它们使用 ByteBuffer 而不是字节数组。
UnsafeOutput、UnsafeInput、UnsafeByteBufferOutput 和 UnsafeByteBufferInput 类的工作方式与其非不安全对应类完全相同,只是它们在许多情况下使用 sun.misc.Unsafe 来获得更高的性能。要使用这些类, Util.unsafe
必须为 true。
使用不安全缓冲区的缺点是执行序列化的系统的本机字节序和数字类型的表示会影响序列化数据。例如,如果数据在X86上写入并在SPARC上读取,则反序列化将失败。此外,如果使用不安全的缓冲区写入数据,则必须使用不安全的缓冲区读取数据。
不安全缓冲区的最大性能差异是在不使用可变长度编码时使用大型原始数组。可以针对不安全的缓冲区或仅针对特定字段禁用可变长度编码(使用 FieldSerializer 时)。
IO 类提供了读取和写入可变长度 int (varint) 和 long (varlong) 值的方法。这是通过使用每个字节的第 8 位来指示后面是否有更多字节来完成的,这意味着 varint 使用 1-5 个字节,varlong 使用 1-9 个字节。使用可变长度编码成本更高,但会使序列化数据更小。
写入可变长度值时,可以针对正值或负值和正值优化该值。例如,当针对正值进行优化时,0 到 127 写入一个字节,128 到 16383 写入两个字节等。但是,小负数在 5 字节时是最坏的情况。当未针对正值进行优化时,这些范围会下移一半。例如,-64到63写在一个字节中,64到8191和-65到-8192写在两个字节中,等等。
输入和输出缓冲区提供了读取和写入固定大小或可变长度值的方法。还有一些方法允许缓冲区决定是写入固定大小还是可变长度值。这允许序列化代码确保可变长度编码用于非常常见的值,如果使用固定大小,这些值会使输出膨胀,同时仍然允许缓冲区配置决定所有其他值。
方法 | 描述 |
---|---|
写Int(int) | 写入 4 字节 int。 |
writeVarInt(int, 布尔值) | 写入 1-5 字节 int。 |
writeInt(int, 布尔值) | 写入 4 或 1-5 字节 int(由缓冲区决定)。 |
写长(长) | 写入 8 字节长。 |
writeVarLong(长整型,布尔值) | 写入 1-9 个字节长。 |
writeLong(长,布尔值) | 写入 8 或 1-9 字节长(由缓冲区决定)。 |
要禁用所有值的可变长度编码,需要重写writeVarInt
、 writeVarLong
、 readVarInt
和readVarLong
方法。
先写入一些数据的长度,然后再写入数据,这可能很有用。当事先不知道数据的长度时,需要将所有数据缓存起来以确定其长度,然后可以写入长度,然后写入数据。为此使用单个大缓冲区会阻止流式传输,并且可能需要不合理的大缓冲区,这并不理想。
分块编码通过使用小缓冲区解决了这个问题。当缓冲区已满时,先写入其长度,然后写入数据。这是一大块数据。缓冲区被清除,这种情况将持续到没有更多数据可写入为止。长度为零的块表示块的结尾。
Kryo 提供了一些类来使分块编码易于使用。 OutputChunked 用于写入分块数据。它扩展了 Output,因此拥有所有写入数据的便捷方法。当 OutputChunked 缓冲区已满时,它会将块刷新到另一个 OutputStream。 endChunk
方法用于标记一组块的结尾。
OutputStream outputStream = new FileOutputStream ( "file.bin" );
OutputChunked output = new OutputChunked ( outputStream , 1024 );
// Write data to output...
output . endChunk ();
// Write more data to output...
output . endChunk ();
// Write even more data to output...
output . endChunk ();
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 ();
一般来说,输出和输入提供良好的性能。如果不安全缓冲区的跨平台不兼容性是可以接受的,那么它们的性能同样好甚至更好,特别是对于原始数组。 ByteBufferOutput 和 ByteBufferInput 提供的性能稍差,但如果字节的最终目的地必须是 ByteBuffer,则这可能是可以接受的。
可变长度编码比固定值慢,尤其是当有大量数据使用它时。
分块编码使用中间缓冲区,因此它会添加所有字节的一份额外副本。仅此一点可能是可以接受的,但是当在可重入序列化器中使用时,序列化器必须为每个对象创建一个 OutputChunked 或 InputChunked 。在序列化期间分配和垃圾收集这些缓冲区可能会对性能产生负面影响。
Kryo 具有三组读取和写入对象的方法。如果对象的具体类未知并且该对象可能为空:
kryo . writeClassAndObject ( output , object );
Object object = kryo . readClassAndObject ( input );
if ( object instanceof SomeClass ) {
// ...
}
如果类已知且对象可能为 null:
kryo . writeObjectOrNull ( output , object );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class );
如果类是已知的并且对象不能为空:
kryo . writeObject ( output , object );
SomeClass object = kryo . readObject ( input , SomeClass . class );
所有这些方法首先找到要使用的适当的序列化器,然后使用它来序列化或反序列化对象。序列化器可以调用这些方法进行递归序列化。 Kryo 自动处理对同一对象的多个引用和循环引用。
除了读取和写入对象的方法之外,Kryo 类还提供了一种注册序列化器、高效读取和写入类标识符、处理不能接受空值的序列化器的空对象以及处理读取和写入对象引用(如果启用)的方法。这使得序列化器可以专注于其序列化任务。
在测试和探索 Kryo API 时,将对象写入字节,然后将这些字节读回对象可能会很有用。
Kryo kryo = new Kryo ();
// Register all classes to be serialized.
kryo . register ( SomeClass . class );
SomeClass object1 = new SomeClass ();
Output output = new Output ( 1024 , - 1 );
kryo . writeObject ( output , object1 );
Input input = new Input ( output . getBuffer (), 0 , output . position ());
SomeClass object2 = kryo . readObject ( input , SomeClass . class );
在此示例中,输出从容量为 1024 字节的缓冲区开始。如果将更多字节写入输出,缓冲区的大小将无限制地增长。 Output 不需要关闭,因为它还没有被赋予 OutputStream。输入直接从输出的byte[]
缓冲区读取。
Kryo 支持使用从一个对象到另一个对象的直接分配来制作对象的深拷贝和浅拷贝。这比序列化为字节然后返回对象更有效。
Kryo kryo = new Kryo ();
SomeClass object = ...
SomeClass copy1 = kryo . copy ( object );
SomeClass copy2 = kryo . copyShallow ( object );
所有正在使用的序列化器都需要支持复制。 Kryo 提供的所有序列化器都支持复制。
与序列化一样,复制时,如果启用了引用,Kryo 会自动处理对同一对象的多个引用和循环引用。
如果仅使用 Kryo 进行复制,则可以安全地禁用注册。
复制对象图后可以使用 Kryo getOriginalToCopyMap
来获取旧对象到新对象的映射。地图由 Kryo reset
自动清除,因此仅当 Kryo setAutoReset
为 false 时才有用。
默认情况下不启用引用。这意味着如果一个对象多次出现在对象图中,它将被多次写入,并将被反序列化为多个不同的对象。当引用被禁用时,循环引用将导致序列化失败。使用用于序列化的 Kryo setReferences
和用于复制的setCopyReferences
启用或禁用引用。
启用引用后,当每个对象第一次出现在对象图中时,会在每个对象之前写入一个 varint。对于同一对象图中该类的后续出现,仅写入 varint。反序列化后,对象引用将恢复,包括任何循环引用。使用的序列化器必须通过在 Serializer read
中调用 Kryo reference
来支持引用。
启用引用会影响性能,因为需要跟踪读取或写入的每个对象。
在幕后,ReferenceResolver 处理已读取或写入的跟踪对象并提供 int 引用 ID。提供了多种实现方式:
ReferenceResolver useReferences(Class)
可以被重写。它返回一个布尔值来决定类是否支持引用。如果类不支持引用,则 varint 引用 ID 不会写在该类型的对象之前。如果某个类不需要引用,并且该类型的对象多次出现在对象图中,则可以通过禁用该类的引用来大大减小序列化大小。默认引用解析器为所有原始包装器和枚举返回 false。对于 String 和其他类,通常也会返回 false,具体取决于要序列化的对象图。
public boolean useReferences ( Class type ) {
return ! Util . isWrapperClass ( type ) && ! Util . isEnum ( type ) && type != String . class ;
}
引用解析器确定单个对象图中引用的最大数量。 Java 数组索引仅限于Integer.MAX_VALUE
,因此使用基于数组的数据结构的引用解析器在序列化超过约 20 亿个对象时可能会导致java.lang.NegativeArraySizeException
。 Kryo 使用 int 类 ID,因此单个对象图中的最大引用数仅限于 int 中的全部正数和负数(约 40 亿)。
Kryo getContext
返回一个用于存储用户数据的映射。 Kryo 实例可供所有序列化器使用,因此所有序列化器都可以轻松访问此数据。
Kryo getGraphContext
类似,但在每个对象图序列化或反序列化后被清除。这使得管理仅与当前对象图相关的状态变得容易。例如,这可以用于在对象图中第一次遇到类时写入一些模式数据。有关示例,请参阅 CompatibleFieldSerializer。
默认情况下,在每个整个对象图序列化后调用 Kryo reset
。这将重置类解析器中未注册的类名、引用解析器中对先前序列化或反序列化对象的引用,并清除图形上下文。 Kryo setAutoReset(false)
可用于禁用自动调用reset
,从而允许该状态跨越多个对象图。
Kryo 是一个促进序列化的框架。框架本身不强制执行模式,也不关心数据写入或读取的内容或方式。序列化器是可插入的,并决定读和写什么。许多串行器都是开箱即用的,可以以各种方式读取和写入数据。虽然提供的序列化器可以读取和写入大多数对象,但它们可以轻松地用您自己的序列化器部分或完全替换。
当 Kryo 编写对象的实例时,首先它可能需要编写一些标识该对象的类的内容。默认情况下,Kryo 将读取或写入的所有类都必须事先注册。注册提供了一个 int 类 ID、用于该类的序列化器以及用于创建该类实例的对象实例化器。
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
在反序列化期间,注册的类必须具有与序列化期间完全相同的 ID。注册时,类会被分配下一个可用的最小整数 ID,这意味着类的注册顺序很重要。可以选择显式指定类 ID,以使顺序变得不重要:
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , 9 );
kryo . register ( AnotherClass . class , 10 );
kryo . register ( YetAnotherClass . class , 11 );
类 ID -1 和 -2 被保留。默认情况下,类 ID 0-8 用于基本类型和字符串,但这些 ID 可以改变用途。 ID 被写为正优化变体,因此当它们是小的正整数时最有效。负 ID 无法有效序列化。
在幕后,ClassResolver 实际处理读取和写入字节来表示类。默认实现在大多数情况下就足够了,但可以替换它来自定义注册类时发生的情况、序列化期间遇到未注册的类时发生的情况以及代表类时读取和写入的内容。
Kryo 可以配置为允许序列化,而无需预先注册类。
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
可以混合使用注册类和未注册类。未注册的类有两个主要缺点:
如果仅使用 Kryo 进行复制,则可以安全地禁用注册。
当不需要注册时,可以启用 Kryo setWarnUnregisteredClasses
在遇到未注册的类时记录消息。这可用于轻松获取所有未注册类的列表。可以重写 Kryo unregisteredClassMessage
以自定义日志消息或采取其他操作。
注册类时,可以选择指定序列化器实例。在反序列化期间,注册的类必须具有与序列化期间完全相同的序列化器和序列化器配置。
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , new SomeSerializer ());
kryo . register ( AnotherClass . class , new AnotherSerializer ());
如果未指定序列化器或遇到未注册的类,则会从将类映射到序列化器的“默认序列化器”列表中自动选择序列化器。拥有许多默认序列化器不会影响序列化性能,因此默认情况下,Kryo 为各种 JRE 类提供 50 多个默认序列化器。可以添加其他默认序列化器:
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
kryo . addDefaultSerializer ( SomeClass . class , SomeSerializer . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
这将导致在注册 SomeClass 或任何扩展或实现 SomeClass 的类时创建 SomeSerializer 实例。
默认序列化器经过排序,因此首先匹配更具体的类,但否则按照添加顺序进行匹配。它们的添加顺序可能与接口相关。
如果没有默认序列化器与类匹配,则使用全局默认序列化器。全局默认序列化器默认设置为 FieldSerializer,但可以更改。通常,全局序列化器可以处理多种不同类型。
Kryo kryo = new Kryo ();
kryo . setDefaultSerializer ( TaggedFieldSerializer . class );
kryo . register ( SomeClass . class );
对于此代码,假设没有默认序列化器与 SomeClass 匹配,则将使用 TaggedFieldSerializer。
类还可以使用 DefaultSerializer 注释,该注释将用于代替选择 Kryo 的默认序列化器之一:
@ DefaultSerializer ( SomeClassSerializer . class )
public class SomeClass {
// ...
}
为了获得最大的灵活性,可以重写 Kryo getDefaultSerializer
以实现用于选择和实例化序列化器的自定义逻辑。
addDefaultSerializer(Class, Class)
方法不允许配置序列化器。可以设置序列化器工厂而不是序列化器类,从而允许工厂创建和配置每个序列化器实例。为常见的序列化器提供工厂,通常使用getConfig
方法来配置创建的序列化器。
Kryo kryo = new Kryo ();
TaggedFieldSerializerFactory defaultFactory = new TaggedFieldSerializerFactory ();
defaultFactory . getConfig (). setReadUnknownTagData ( true );
kryo . setDefaultSerializer ( defaultFactory );
FieldSerializerFactory someClassFactory = new FieldSerializerFactory ();
someClassFactory . getConfig (). setFieldsCanBeNull ( false );
kryo . register ( SomeClass . class , someClassFactory );
序列化器工厂有一个isSupported(Class)
方法,该方法允许它拒绝处理某个类,即使它与该类匹配。这允许工厂检查多个接口或实现其他逻辑。
虽然某些序列化器适用于特定类,但其他序列化器可以序列化许多不同的类。序列化器可以使用 Kryo newInstance(Class)
创建任何类的实例。这是通过查找类的注册,然后使用注册的 ObjectInstantiator 来完成的。实例化器可以在注册时指定。
Registration registration = kryo . register ( SomeClass . class );
registration . setInstantiator ( new ObjectInstantiator < SomeClass >() {
public SomeClass newInstance () {
return new SomeClass ( "some constructor arguments" , 1234 );
}
});
如果注册没有实例化器,则由 Kryo newInstantiator
提供。要自定义对象的创建方式,可以覆盖 Kryo newInstantiator
或提供 InstantiatorStrategy。
Kryo 提供了 DefaultInstantiatorStrategy,它使用 ReflectASM 创建对象来调用零参数构造函数。如果不可能,它会使用反射来调用零参数构造函数。如果也失败,那么它要么抛出异常,要么尝试后备 InstantiatorStrategy。反射使用setAccessible
,因此私有零参数构造函数可以是允许 Kryo 创建类实例而不影响公共 API 的好方法。
DefaultInstantiatorStrategy 是使用 Kryo 创建对象的推荐方法。它运行构造函数就像使用 Java 代码一样。另外,语言外的机制也可以用来创建对象。 Objenesis StdInstantiatorStrategy 使用 JVM 特定的 API 来创建类的实例,而无需调用任何构造函数。使用它是危险的,因为大多数类都希望调用它们的构造函数。通过绕过构造函数来创建对象可能会使对象处于未初始化或无效状态。类必须设计为以这种方式创建。
Kryo 可以配置为首先尝试 DefaultInstantiatorStrategy,然后在必要时回退到 StdInstantiatorStrategy。
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new StdInstantiatorStrategy ()));
另一个选择是 SerializingInstantiatorStrategy,它使用 Java 内置的序列化机制来创建实例。使用此方法,类必须实现 java.io.Serializable 并调用超类中的第一个零参数构造函数。这也会绕过构造函数,因此是危险的,原因与 StdInstantiatorStrategy 相同。
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new SerializingInstantiatorStrategy ()));
或者,一些通用序列化程序提供可以重写的方法来自定义特定类型的对象创建,而不是调用 Kryo newInstance
。
kryo . register ( SomeClass . class , new FieldSerializer ( kryo , SomeClass . class ) {
protected T create ( Kryo kryo , Input input , Class <? extends T > type ) {
return new SomeClass ( "some constructor arguments" , 1234 );
}
});
一些序列化程序提供了writeHeader
方法,可以重写该方法以在正确的时间写入create
所需的数据。
static public class TreeMapSerializer extends MapSerializer < TreeMap > {
protected void writeHeader ( Kryo kryo , Output output , TreeMap map ) {
kryo . writeClassAndObject ( output , map . comparator ());
}
protected TreeMap create ( Kryo kryo , Input input , Class <? extends TreeMap > type , int size ) {
return new TreeMap (( Comparator ) kryo . readClassAndObject ( input ));
}
}
如果序列化器不提供writeHeader
,则可以在write
中为create
写入数据。
static public class SomeClassSerializer extends FieldSerializer < SomeClass > {
public SomeClassSerializer ( Kryo kryo ) {
super ( kryo , SomeClass . class );
}
public void write ( Kryo kryo , Output output , SomeClass object ) {
output . writeInt ( object . value );
}
protected SomeClass create ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
return new SomeClass ( input . readInt ());
}
}
即使序列化程序知道值的预期类(例如字段的类),如果该值的具体类不是最终的,则序列化程序需要首先写入类 ID,然后写入值。最终类可以更有效地序列化,因为它们是非多态的。
Kryo isFinal
用于确定一个类是否是最终类。即使对于非最终类型,也可以重写此方法以返回 true。例如,如果应用程序广泛使用 ArrayList 但从不使用 ArrayList 子类,则将 ArrayList 视为 Final 可以允许 FieldSerializer 为每个 ArrayList 字段节省 1-2 个字节。
Kryo 可以序列化实现 java.io.Serialized 的 Java 8+ 闭包,但有一些注意事项。在一个 JVM 上序列化的闭包可能无法在另一 JVM 上反序列化。
Kryo isClosure
用于确定一个类是否是闭包。如果是这样,则使用 ClosureSerializer.Closure 来查找类注册而不是闭包的类。要序列化闭包,必须注册以下类:ClosureSerializer.Closure、Object[] 和 Class。此外,必须注册闭包的捕获类。
kryo . register ( Object []. class );
kryo . register ( Class . class );
kryo . register ( ClosureSerializer . Closure . class , new ClosureSerializer ());
kryo . register ( CapturingClass . class );
Callable < Integer > closure1 = ( Callable < Integer > & java . io . Serializable )( () -> 72363 );
Output output = new Output ( 1024 , - 1 );
kryo . writeObject ( output , closure1 );
Input input = new Input ( output . getBuffer (), 0 , output . position ());
Callable < Integer > closure2 = ( Callable < Integer >) kryo . readObject ( input , ClosureSerializer . Closure . class );
通过一些努力,可以序列化未实现 Serialized 的闭包。
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。这些序列化器包装另一个序列化器来编码和解码字节。
Serializer 抽象类定义了从对象到字节以及从字节到对象的方法。
public class ColorSerializer extends Serializer < Color > {
public void write ( Kryo kryo , Output output , Color color ) {
output . writeInt ( color . getRGB ());
}
public Color read ( Kryo kryo , Input input , Class <? extends Color > type ) {
return new Color ( input . readInt ());
}
}
序列化器只有两个必须实现的方法。 write
将对象作为字节写入输出。 read
创建对象的新实例并从输入中读取以填充它。
当 Kryo 用于在 Serializer read
中读取嵌套对象时,如果嵌套对象可以引用父对象,则必须首先使用父对象调用 Kryo reference
。如果嵌套对象不可能引用父对象、如果 Kryo 未用于嵌套对象或未使用引用,则无需调用 Kryo reference
。如果嵌套对象可以使用相同的序列化程序,则该序列化程序必须是可重入的。
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
SomeClass object = new SomeClass ();
kryo . reference ( object );
// Read objects that may reference the SomeClass instance.
object . someField = kryo . readClassAndObject ( input );
return object ;
}
序列化器通常不应直接使用其他序列化器,而应使用 Kryo 读写方法。这允许 Kryo 编排序列化并处理引用和空对象等功能。有时,序列化程序知道对嵌套对象使用哪个序列化程序。在这种情况下,它应该使用 Kryo 接受序列化器的读写方法。
如果对象可以为空:
Serializer serializer = ...
kryo . writeObjectOrNull ( output , object , serializer );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class , serializer );
如果对象不能为空:
Serializer serializer = ...
kryo . writeObject ( output , object , serializer );
SomeClass object = kryo . readObject ( input , SomeClass . class , serializer );
在序列化期间,Kryo getDepth
提供对象图的当前深度。
当序列化失败时,可以抛出 KryoException 以及有关对象图中异常发生位置的序列化跟踪信息。使用嵌套序列化器时,可以捕获 KryoException 以添加序列化跟踪信息。
Object object = ...
Field [] fields = ...
for ( Field field : fields ) {
try {
// Use other serializers to serialize each field.
} catch ( KryoException ex ) {
ex . addTrace ( field . getName () + " (" + object . getClass (). getName () + ")" );
throw ex ;
} catch ( Throwable t ) {
KryoException ex = new KryoException ( t );
ex . addTrace ( field . getName () + " (" + object . getClass (). getName () + ")" );
throw ex ;
}
}
Kryo 提供的序列化程序在序列化嵌套对象时使用调用堆栈。 Kryo 最大限度地减少了堆栈调用,但对于极深的对象图可能会发生堆栈溢出。这是大多数序列化库(包括内置 Java 序列化)的常见问题。可以使用-Xss
增加堆栈大小,但请注意,这适用于所有线程。具有许多线程的 JVM 中的大堆栈可能会使用大量内存。
Kryo setMaxDepth
可用于限制对象图的最大深度。这可以防止恶意数据导致堆栈溢出。
默认情况下,序列化器永远不会收到 null,而是 Kryo 会根据需要写入一个字节来表示 null 或非 null。如果序列化程序可以通过本身处理 null 来提高效率,则它可以调用 Serializer setAcceptsNull(true)
。当已知序列化器将处理的所有实例永远不会为空时,这也可以用于避免写入表示字节的空值。
Kryo getGenerics
提供泛型类型信息,因此序列化器可以更加高效。这最常用于避免在类型参数类为最终类型时编写类。
通用类型推断默认启用,可以使用 Kryo setOptimizedGenerics(false)
禁用。禁用泛型优化可以提高性能,但代价是序列化大小更大。
如果该类具有单个类型参数, nextGenericClass
返回类型参数类,如果没有,则返回 null。读取或写入任何嵌套对象后,必须调用popGenericType
。有关示例,请参阅 CollectionSerializer。
public class SomeClass < T > {
public T value ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class valueClass = kryo . getGenerics (). nextGenericClass ();
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . getGenerics (). popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class valueClass = kryo . getGenerics (). nextGenericClass ();
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
kryo . getGenerics (). popGenericType ();
return object ;
}
}
对于具有多个类型参数的类, nextGenericTypes
返回 GenericType 实例的数组,并使用resolve
来获取每个 GenericType 的类。读取或写入任何嵌套对象后,必须调用popGenericType
。有关示例,请参阅 MapSerializer。
public class SomeClass < K , V > {
public K key ;
public V value ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class keyClass = null , valueClass = null ;
GenericType [] genericTypes = kryo . getGenerics (). nextGenericTypes ();
if ( genericTypes != null ) {
keyClass = genericTypes [ 0 ]. resolve ( kryo . getGenerics ());
valueClass = genericTypes [ 1 ]. resolve ( kryo . getGenerics ());
}
if ( keyClass != null && kryo . isFinal ( keyClass )) {
Serializer serializer = kryo . getSerializer ( keyClass );
kryo . writeObjectOrNull ( output , object . key , serializer );
} else
kryo . writeClassAndObject ( output , object . key );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . getGenerics (). popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class keyClass = null , valueClass = null ;
GenericType [] genericTypes = kryo . getGenerics (). nextGenericTypes ();
if ( genericTypes != null ) {
keyClass = genericTypes [ 0 ]. resolve ( kryo . getGenerics ());
valueClass = genericTypes [ 1 ]. resolve ( kryo . getGenerics ());
}
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( keyClass != null && kryo . isFinal ( keyClass )) {
Serializer serializer = kryo . getSerializer ( keyClass );
object . key = kryo . readObjectOrNull ( input , keyClass , serializer );
} else
object . key = kryo . readClassAndObject ( input );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
kryo . getGenerics (). popGenericType ();
return object ;
}
}
对于传递对象图中嵌套对象的类型参数信息的序列化程序(有些高级用法),首先使用 GenericsHierarchy 来存储类的类型参数。在序列化期间,在解析泛型类型(如果有)之前调用泛型pushTypeVariables
。如果返回 >0,则后面必须跟泛型popTypeVariables
。有关示例,请参阅 FieldSerializer。
public class SomeClass < T > {
T value ;
List < T > list ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
private final GenericsHierarchy genericsHierarchy ;
public SomeClassSerializer () {
genericsHierarchy = new GenericsHierarchy ( SomeClass . class );
}
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class valueClass = null ;
Generics generics = kryo . getGenerics ();
int pop = 0 ;
GenericType [] genericTypes = generics . nextGenericTypes ();
if ( genericTypes != null ) {
pop = generics . pushTypeVariables ( genericsHierarchy , genericTypes );
valueClass = genericTypes [ 0 ]. resolve ( generics );
}
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . writeClassAndObject ( output , object . list );
if ( pop > 0 ) generics . popTypeVariables ( pop );
generics . popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class valueClass = null ;
Generics generics = kryo . getGenerics ();
int pop = 0 ;
GenericType [] genericTypes = generics . nextGenericTypes ();
if ( genericTypes != null ) {
pop = generics . pushTypeVariables ( genericsHierarchy , genericTypes );
valueClass = genericTypes [ 0 ]. resolve ( generics );
}
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
object . list = ( List ) kryo . readClassAndObject ( input );
if ( pop > 0 ) generics . popTypeVariables ( pop );
generics . popGenericType ();
return object ;
}
}
类可以选择通过实现 KryoSerialized(类似于 java.io.Externalizable)来进行自己的序列化,而不是使用序列化器。
public class SomeClass implements KryoSerializable {
private int value ;
public void write ( Kryo kryo , Output output ) {
output . writeInt ( value , false );
}
public void read ( Kryo kryo , Input input ) {
value = input . readInt ( false );
}
}
显然,在调用read
之前必须已经创建实例,因此该类无法控制自己的创建。 KryoSerialized 类将使用默认序列化器 KryoSerializedSerializer,它使用 Kryo newInstance
创建新实例。编写自己的序列化程序来自定义流程、在序列化之前或之后调用方法等都很简单。
仅当copy
被覆盖时,序列化程序才支持复制。与 Serializer read
类似,此方法包含创建和配置副本的逻辑。就像read
一样,如果任何子对象可以引用父对象,则必须在使用 Kryo 复制子reference
之前调用 Kryo 引用。
class SomeClassSerializer extends Serializer < SomeClass > {
public SomeClass copy ( Kryo kryo , SomeClass original ) {
SomeClass copy = new SomeClass ();
kryo . reference ( copy );
copy . intValue = original . intValue ;
copy . object = kryo . copy ( original . object );
return copy ;
}
}
类可以实现 KryoCopyable 来进行自己的复制,而不是使用序列化器:
public class SomeClass implements KryoCopyable < SomeClass > {
public SomeClass copy ( Kryo kryo ) {
SomeClass copy = new SomeClass ();
kryo . reference ( copy );
copy . intValue = intValue ;
copy . object = kryo . copy ( object );
return copy ;
}
}
当类型不可变时,可以使用序列化器setImmutable(true)
。在这种情况下,不需要实现序列化器copy
——默认的copy
实现将返回原始对象。
以下经验规则适用于 Kryo 的版本编号:
升级任何依赖项都是一个重要事件,但序列化库比大多数依赖项更容易损坏。升级 Kryo 时,请检查版本差异并在您自己的应用程序中彻底测试新版本。我们尽力使其尽可能安全和简单。
默认提供的 Kryo 序列化程序假定将使用 Java 进行反序列化,因此它们没有显式定义写入的格式。序列化器可以使用更容易被其他语言读取的标准化格式编写,但这不是默认提供的。
对于某些需求,例如序列化字节的长期存储,序列化如何处理类的更改可能很重要。这称为前向兼容性(读取由较新的类序列化的字节)和后向兼容性(读取由较旧的类序列化的字节)。 Kryo 提供了一些通用序列化器,它们采用不同的方法来处理兼容性。可以轻松开发其他序列化器以实现向前和向后兼容性,例如使用外部手写模式的序列化器。
当类的更改超出其序列化程序可以处理的范围时,可以编写序列化程序以将数据传输到不同的类。应用程序代码中所有旧类的使用都应替换为新类。旧类仅为该序列化器保留。
kryo . register ( OldClass . class , new TaggedFieldSerializer ( kryo , OldClass . class ) {
public Object read ( Kryo kryo , Input input , Class type ) {
OldClass oldObject = ( OldClass ) super . read ( kryo , input , OldClass . class );
NewClass newObject = new NewClass ();
// Use data from the old class to populate the instance of the new class and return it.
return newObject ;
}
});
kryo . register ( NewClass . class );
Kryo 提供了许多具有各种配置选项和兼容性级别的序列化器。其他序列化器可以在 kryo-serializers 姊妹项目中找到,该项目托管访问私有 API 的序列化器,否则在所有 JVM 上都不是完全安全的。更多序列化器可以在链接部分找到。
FieldSerializer 的工作原理是序列化每个非瞬态字段。它可以序列化 POJO 和许多其他类,无需任何配置。默认情况下会写入和读取所有非公共字段,因此评估将序列化的每个类非常重要。如果字段是公共的,序列化可能会更快。
FieldSerializer 使用 Java 类文件作为架构,仅写入字段数据,无需任何架构信息,因此非常高效。它不支持在不使先前序列化的字节无效的情况下添加、删除或更改字段类型。仅当不更改字段的字母顺序时才允许重命名字段。
FieldSerializer 的兼容性缺点在许多情况下是可以接受的,例如通过网络发送数据时,但对于长期数据存储来说可能不是一个好的选择,因为 Java 类无法发展。在许多情况下,TaggedFieldSerializer 是更好的选择。
环境 | 描述 | 默认值 |
---|---|---|
fieldsCanBeNull | 当为 false 时,假设没有字段值为空,这可以为每个字段节省 0-1 个字节。 | 真的 |
setFieldsAsAccessible | 如果为 true,则所有非瞬态字段(包括私有字段)都将被序列化,并在必要时setAccessible 。如果为 false,则仅序列化公共 API 中的字段。 | 真的 |
ignoreSyntheticFields | 如果为 true,则合成字段(由编译器生成用于范围界定)被序列化。 | 错误的 |
fixedFieldTypes | 如果为 true,则假定每个字段值的具体类型与该字段的类型相匹配。这样就无需为字段值写入类 ID。 | 错误的 |
copyTransient | 如果为 true,则将复制所有瞬态字段。 | 真的 |
serializeTransient | 如果为 true,瞬态字段将被序列化。 | 错误的 |
variableLengthEncoding | 如果为 true,则可变长度值用于 int 和 long 字段。 | 真的 |
extendedFieldNames | 如果为 true,则字段名称以其声明类为前缀。当子类具有与超类同名的字段时,这可以避免冲突。 | 错误的 |
FieldSerializer 提供将被序列化的字段。字段可以被删除,因此它们不会被序列化。可以配置字段以使序列化更加高效。
FieldSerializer fieldSerializer = ...
fieldSerializer . removeField ( "id" ); // Won't be serialized.
CachedField nameField = fieldSerializer . getField ( "name" );
nameField . setCanBeNull ( false );
CachedField someClassField = fieldSerializer . getField ( "someClass" );
someClassField . setClass ( SomeClass . class , new SomeClassSerializer ());
环境 | 描述 | 默认值 |
---|---|---|
canBeNull | 当为 false 时,假定字段值永远不为空,这可以节省 0-1 个字节。 | 真的 |
valueClass | 设置用于字段值的具体类和序列化器。这样就无需为值写入类 ID。如果字段值的类是原始类、原始包装类或最终类,则此设置默认为字段的类。 | 无效的 |
serializer | 设置用于字段值的序列化器。如果设置了序列化程序,某些序列化程序还需要设置值类。如果为 null,则将使用向 Kryo 注册的字段值类的序列化程序。 | 无效的 |
variableLengthEncoding | 如果为 true,则使用可变长度值。这仅适用于 int 或 long 字段。 | 真的 |
optimizePositive | 如果为 true,则针对可变长度值优化正值。当使用可变长度编码时,这仅适用于 int 或 long 字段。 | 真的 |
注释可用于配置每个字段的序列化器。
注解 | 描述 |
---|---|
@Bind | 设置任何字段的 CachedField 设置。 |
@CollectionBind | 设置 Collection 字段的 CollectionSerializer 设置。 |
@MapBind | 设置 Map 字段的 MapSerializer 设置。 |
@NotNull | 将字段标记为永不为空。 |
public class SomeClass {
@ NotNull
@ Bind ( serializer = StringSerializer . class , valueClass = String . class , canBeNull = false )
Object stringField ;
@ Bind ( variableLengthEncoding = false )
int intField ;
@ BindMap (
keySerializer = StringSerializer . class ,
valueSerializer = IntArraySerializer . class ,
keyClass = String . class ,
valueClass = int []. class ,
keysCanBeNull = false )
Map map ;
@ BindCollection (
elementSerializer = LongArraySerializer . class ,
elementClass = long []. class ,
elementsCanBeNull = false )
Collection collection ;
}
VersionFieldSerializer 扩展了 FieldSerializer 并提供向后兼容性。这意味着可以添加字段而不会使先前序列化的字节无效。不支持删除、重命名或更改字段类型。
当添加一个字段时,它必须有@Since(int)
注解来指示它添加的版本,以便与之前序列化的字节兼容。注释值绝不能改变。
VersionFieldSerializer 为 FieldSerializer 添加了很少的开销:一个额外的 varint。
环境 | 描述 | 默认值 |
---|---|---|
compatible | 当为 false 时,读取不同版本的对象时会引发异常。对象的版本是任何字段的最大版本。 | 真的 |
VersionFieldSerializer 也继承了 FieldSerializer 的所有设置。
TaggedFieldSerializer 扩展了 FieldSerializer 以提供向后兼容性和可选的向前兼容性。这意味着可以添加或重命名字段,也可以选择删除字段,而不会导致先前序列化的字节无效。不支持更改字段类型。
仅序列化具有@Tag(int)
注释的字段。字段标记值在类及其所有超类中都必须是唯一的。如果遇到重复的标记值,则会引发异常。
向前和向后兼容性以及序列化性能取决于readUnknownTagData
和chunkedEncoding
设置。此外,在标签值的每个字段之前写入 varint。
当readUnknownTagData
和chunkedEncoding
为 false 时,不得删除字段,但可以应用@Deprecated
注释。读取旧字节时会读取已弃用的字段,但不会写入新字节。类可以通过读取已弃用字段的值并将其写入其他地方来发展。字段可以被重命名和/或设为私有,以减少类中的混乱(例如, ignored1
、 ignored2
)。
TaggedFieldSerializer(带有readUnknownTagData
和chunkedEncoding
false)是大多数可以注释字段的类的建议序列化器。它允许类发展并从序列化数据中删除字段(通过弃用),满足大多数应用程序的需求,而不会增加太多序列化大小。
环境 | 描述 | 默认值 |
---|---|---|
readUnknownTagData | 当为 false 且遇到未知标记时,会引发异常,或者如果chunkedEncoding 为 true,则会跳过数据。如果为 true,则每个字段值的类都写在该值之前。当遇到未知标签时,会尝试读取数据。这用于跳过数据,如果启用了引用,则仍然可以反序列化对象图中引用该数据的任何其他值。如果读取数据失败(例如类未知或已被删除),则抛出异常,或者如果 chunkedEncoding 为 true,则跳过数据。在任一情况下,如果跳过数据并启用引用,则不会读取跳过的数据中的任何引用,并且进一步的反序列化可能会收到错误的引用并失败。 | 错误的 |
chunkedEncoding | 如果为 true,则使用分块编码写入字段,以允许跳过未知字段数据。这会影响性能。 | 错误的 |
chunkSize | 分块编码的每个块的最大大小。 | 1024 |
TaggedFieldSerializer 也继承了 FieldSerializer 的所有设置。
CompatibleFieldSerializer 扩展了 FieldSerializer 以提供向前和向后兼容性。这意味着可以添加或删除字段,而不会导致先前序列化的字节无效。不支持重命名或更改字段类型。与 FieldSerializer 一样,它可以序列化大多数类,而无需注释。
向前和向后兼容性以及序列化性能取决于readUnknownFieldData
和chunkedEncoding
设置。此外,第一次在序列化字节中遇到该类时,会编写一个包含字段名称字符串的简单模式。因为字段数据是通过名称来标识的,所以如果超类有一个与子类同名的字段,则extendedFieldNames
必须为true。
环境 | 描述 | 默认值 |
---|---|---|
readUnknownFieldData | 当 false 且遇到未知字段时,会引发异常,或者如果chunkedEncoding 为 true,则会跳过数据。如果为 true,则每个字段值的类都写在该值之前。当遇到未知字段时,会尝试读取数据。这用于跳过数据,如果启用了引用,则仍然可以反序列化对象图中引用该数据的任何其他值。如果读取数据失败(例如类未知或已被删除),则抛出异常,或者如果 chunkedEncoding 为 true,则跳过数据。在任一情况下,如果跳过数据并启用引用,则不会读取跳过的数据中的任何引用,并且进一步的反序列化可能会收到错误的引用并失败。 | 真的 |
chunkedEncoding | 如果为 true,则使用分块编码写入字段,以允许跳过未知字段数据。这会影响性能。 | 错误的 |
chunkSize | 分块编码的每个块的最大大小。 | 1024 |
CompatibleFieldSerializer 也继承了 FieldSerializer 的所有设置。
BeanSerializer 与 FieldSerializer 非常相似,不同之处在于它使用 bean getter 和 setter 方法而不是直接字段访问。这稍微慢一些,但可能更安全,因为它使用公共 API 来配置对象。与 FieldSerializer 一样,它不提供向前或向后兼容性。
CollectionSerializer 序列化实现 java.util.Collection 接口的对象。
环境 | 描述 | 默认值 |
---|---|---|
elementsCanBeNull | 当为 false 时,假设集合中没有元素为 null,这可以为每个元素节省 0-1 个字节。 | 真的 |
elementClass | 设置用于集合中每个元素的具体类。这样就无需为每个元素写入类 ID。如果元素类是已知的(例如通过泛型)并且是原始的、原始包装器或最终的,则即使此设置为 null,CollectionSerializer 也不会写入类 ID。 | 无效的 |
elementSerializer | 设置用于集合中每个元素的序列化程序。如果设置了序列化程序,某些序列化程序还需要设置值类。如果为 null,则将使用为每个元素的类向 Kryo 注册的序列化器。 | 无效的 |
MapSerializer 序列化实现 java.util.Map 接口的对象。
环境 | 描述 | 默认值 |
---|---|---|
keysCanBeNull | 当为 false 时,假设映射中没有键为空,这可以为每个条目节省 0-1 个字节。 | 真的 |
valuesCanBeNull | 当为 false 时,假定映射中没有值为空的值,这可以为每个条目节省 0-1 个字节。 | 真的 |
keyClass | 设置用于映射中每个键的具体类。这样就无需为每个键写入类 ID。 | 无效的 |
valueClass | 设置用于映射中每个值的具体类。这样就无需为每个值写入类 ID。 | 无效的 |
keySerializer | 设置用于映射中每个键的序列化器。如果设置了值序列化程序,则某些序列化程序还需要设置值类。如果为 null,则将使用为每个键的类向 Kryo 注册的序列化器。 | 无效的 |
valueSerializer | 设置用于映射中每个值的序列化器。如果设置了键序列化程序,某些序列化程序还需要设置值类。如果为 null,则将使用为每个值的类向 Kryo 注册的序列化器。 | 无效的 |
JavaSerializer 和ExternalizedSerializer 是Kryo 序列化程序,它使用Java 的内置序列化。这与通常的 Java 序列化一样慢,但对于遗留类可能是必需的。
java.io.Externalizable 和 java.io.Serialized 默认没有设置默认序列化器,因此必须手动设置默认序列化器或在注册类时设置的序列化器。
class SomeClass implements Externalizable { /* ... */ }
kryo . addDefaultSerializer ( Externalizable . class , ExternalizableSerializer . class );
kryo . register ( SomeClass . class );
kryo . register ( SomeClass . class , new JavaSerializer ());
kryo . register ( SomeClass . class , new ExternalizableSerializer ());
Kryo 利用低开销、轻量级的 MinLog 日志记录库。可以通过以下方法之一设置日志记录级别:
Log . ERROR ();
Log . WARN ();
Log . INFO ();
Log . DEBUG ();
Log . TRACE ();
Kryo 不会在INFO
(默认)及以上级别进行日志记录。 DEBUG
在开发过程中很方便。在调试特定问题时, TRACE
很容易使用,但是通常会输出太多信息以至于无法继续使用。
MinLog支持固定的记录级别,这会导致Java编译器在编译时删除低于该级别的记录语句。 Kryo必须用固定的记录级MinLog Jar编译。
Kryo不是线程的安全。每个线程应具有自己的Kryo,输入和输出实例。
由于Kryo不是线程安全的,并且可以考虑在多线程环境中或池化中构建和配置Kryo实例相对昂贵。
static private final ThreadLocal < Kryo > kryos = new ThreadLocal < Kryo >() {
protected Kryo initialValue () {
Kryo kryo = new Kryo ();
// Configure the Kryo instance.
return kryo ;
};
};
Kryo kryo = kryos . get ();
为了汇总,Kryo提供了可以将Kryo,输入,输出或任何其他类实例的池类提供。
// Pool constructor arguments: thread safe, soft references, maximum capacity
Pool < Kryo > kryoPool = new Pool < Kryo >( true , false , 8 ) {
protected Kryo create () {
Kryo kryo = new Kryo ();
// Configure the Kryo instance.
return kryo ;
}
};
Kryo kryo = kryoPool . obtain ();
// Use the Kryo instance here.
kryoPool . free ( kryo );
Pool < Output > outputPool = new Pool < Output >( true , false , 16 ) {
protected Output create () {
return new Output ( 1024 , - 1 );
}
};
Output output = outputPool . obtain ();
// Use the Output instance here.
outputPool . free ( output );
Pool < Input > inputPool = new Pool < Input >( true , false , 16 ) {
protected Input create () {
return new Input ( 1024 );
}
};
Input input = inputPool . obtain ();
// Use the Input instance here.
inputPool . free ( input );
如果将true
作为第一个参数传递给池构造函数,则池在内部使用同步,可以通过多个线程同时访问。
如果true
作为第二个参数传递给 Pool 构造函数,则 Pool 使用 java.lang.ref.SoftReference 存储对象。这允许在 JVM 内存压力较高时对池中的对象进行垃圾回收。池clean
删除其对象已被垃圾收集的所有软引用。当未设置最大容量时,这可以减小池的大小。当池有最大容量时,没有必要调用clean
因为如果达到最大容量,Pool free
将尝试删除空引用。
第三个Pool参数是最大容量。如果一个对象被释放并且池中已包含最大数量的空闲对象,则指定的对象将被重置但不会添加到池中。最大容量可以无限制地省略。
如果对象实现了 Pool.Poolable,则在释放对象时会调用 Poolable reset
。这使对象有机会重置其状态以供将来重用。或者,可以覆盖池reset
以重置对象。输入和输出实现可池以设置其position
并total
为0。Kryo无法实现池,因为其对象图状态通常在每个序列化之后自动重置(请参见RESET)。如果您通过setAutoReset(false)
禁用自动重置,请确保在将实例返回到池之前调用Kryo.reset()
。
Pool getFree
返回可获得的对象数量。如果使用软引用,该数字可能包括已被垃圾收集的对象。可以首先使用clean
来删除空的软引用。
Pool getPeak
返回历史最高数量的空闲对象。这可以帮助确定池的最大容量是否设置得当。它可以随时使用resetPeak
重置。
Kryo提供了许多基于JMH的基准和R/GGPLOT2文件。
可以将Kryo与JVM Serializers项目中的许多其他序列化库进行比较。基准很小,过时和本土,而不是使用JMH,因此值得信赖。同样,很难使用基准测试彻底比较序列化库。图书馆具有许多不同的功能,并且通常具有不同的目标,因此他们可能会擅长解决完全不同的问题。为了了解这些基准,应分析正在运行的代码和序列化的数据与您的特定需求进行鲜明对比。一些序列化器是高度优化的,并使用代码的页面,另一些仅使用几行。这很好地展示了可能的东西,但对于许多情况来说可能不是相关的比较。
使用Kryo有许多项目。下面列出了一些。如果您想此处包含您的项目,请提交拉动请求。